T-SQL—理解CTEs

T-SQL—理解CTEs

 

在推出SQLServer2005之后,微软定义了一个新的查询架构叫做公共表表达式–CTE。CTE是一个基于简单查询的临时结果集,在一个简单的插入、更新、删除或者select语句的执行范围内使用。再本篇中,我们将看到如何定义和使用CTE。

定义和使用CTE

通过使用CTE你能写和命名一个T-SQL select 语句,然后引用这个命名的语句就像使用一个表或者试图一样。

CTE下面就是定义一个CTE的语法:

WITH <expression_name> (Column1, Column2, …) AS (CTE Definition)

表示:

  • <expression_name>”   CTE的命名
  • “Column 1, Column2,…”  查询语句返回结果集的列名称
  • “CTE Definition”             select语句返回的结果集.

定义CTE需要跟着一个INSERT, UPDATE, DELETE, 或者SELECT的语句来引用CTE。假如CTE是一个批处理的一部分,那么语句之前用一个With开始然后以分号结束。当你定义了一个多重CTE,即一个CTE引用另一个CTE则需要被引用的CTE定义在引用的CTE之前。听起来可能有点混乱,那我们闲话少说看实例来说明吧。

下面是一些在CTE中可以被使用的选项:

  • ORDER BY (当使用top的时候可以使用)
  • INTO
  • OPTION (带有查询提示)
  • FOR XML
  • FOR BROWSE

递归CTE语句

我理解递归就是调用自己的过程。每一个递归处理的迭代都返回一个结果的子集。这个递归处理保持循环调用直至达到条件限制才停止。最终的结果集其实就是CTE循环中每一个调用超生的结果集的并集。

递归CTE,包含了至少两个查询定义,一个是select语句,另一个查询被作为“锚成员”,而其他的查询定义被作为循环成员。锚成员查询定义不包含CTE而循环成员中包括。另外,锚成员查询需要出现在CTE递归成员查询之前,且两者返回的列完全相同。可以有多个锚成员查询,其中每一个都需要与UNION ALL, UNION, INTERSECT, 或者 EXCEPT联合使用。当然也有多重的递归查询定义,每一个递归查询定义一定与UNION ALL联合使用。UNION ALL 操作符被用来连接最后的锚查询与第一个递归查询。接下来我们用实际立在来讨论一下CTE和递归CTE。

Example of a Simple CTE

如前所述,CTE 提供了一种能更好书写你的复杂代码的方法,提高了代码可读性。如下面的复杂的代码

USE AdventureWorks2012;
GO
SELECT YearMonth, ProductID, SumLineTotal FROM
( SELECT CONVERT(CHAR(7),ModifiedDate,120) AS YearMonth , ProductID , SUM(LineTotal) AS SumLineTotal FROM Sales.SalesOrderDetail
GROUP BY ProductId, CONVERT(CHAR(7),ModifiedDate,120) )
MonthlyProductSales WHERE YearMonth = ‘2008-06’;

 

代码是一个select语句,有一个子查询在FROM后面的子句中。子查询被当做一个派生表 MonthlyProductSales,查询表按照根据ModifiedDate的月和年粒度进行汇总,将LineTotal 金额加在一起。在筛选出年和月份为“2008-06”的结果后进行分组汇总。

接下来我们用CTE来实现上述的代码。

USE AdventureWorks2012;
GO
— CTE 定义
WITH MonthlyProductSales AS (
SELECT CONVERT(CHAR(7),ModifiedDate,120) AS YearMonth , ProductID , SUM(LineTotal) AS SumLineTotal FROM Sales.SalesOrderDetail GROUP BY ProductId, CONVERT(CHAR(7),ModifiedDate,120) )
— 包含CTE的select语句
SELECT * FROM MonthlyProductSales WHERE YearMonth = ‘2008-06’;

在这个代码中,我将衍生表子查询放到了CTE命名为MonthlyProductSales 的里面,然后取代了子查询,在我的Select语句中调用CTE命名的表MonthlyProductSales,这样是不是显得更加容易理解和维护了?

使用多重CTE的例子

 

假如你的代码更加复杂并且包含多个子查询,你就得考虑重写来简化维护和提高易读性。重写的方式之一就是讲子查询重写成CTEs。为了更好地展示,先看一下下面的非CTE复杂查询如下:

USE AdventureWorks2012;
GO
SELECT
SalesPersonID ,
SalesYear ,
TotalSales ,
SalesQuotaYear ,
SalesQuota FROM
( –第一个子查询
SELECT
SalesPersonID ,
SUM(TotalDue) AS TotalSales ,
YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
GROUP BY SalesPersonID, YEAR(OrderDate) ) AS Sales
JOIN ( — 第二个子查询
SELECT
BusinessEntityID ,
SUM(SalesQuota)AS SalesQuota ,
YEAR(QuotaDate) AS SalesQuotaYear
FROM Sales.SalesPersonQuotaHistory
GROUP BY BusinessEntityID, YEAR(QuotaDate) )
AS Sales_Quota ON Sales_Quota.BusinessEntityID = Sales.SalesPersonID
AND Sales_Quota.SalesQuotaYear = Sales.SalesYear
ORDER BY SalesPersonID, SalesYear;

我直接上代码啊,看看如何通过CTE来简化这个代码。

USE AdventureWorks2012;
GO
WITH
— 第一个被CTE重写的子查询
WITH Sales AS (
SELECT
SalesPersonID ,
SUM(TotalDue) AS TotalSales ,
YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
GROUP BY SalesPersonID, YEAR(OrderDate) ),
— 第二个被CTE重写的子查询
Sales_Quota AS (
SELECT
BusinessEntityID ,
SUM(SalesQuota)AS SalesQuota ,
YEAR(QuotaDate) AS SalesQuotaYear
FROM Sales.SalesPersonQuotaHistory
GROUP BY BusinessEntityID, YEAR(QuotaDate) )
— SELECT 使用多重CTEs
SELECT
SalesPersonID ,
SalesYear ,
TotalSales ,
SalesQuotaYear ,
SalesQuota
FROM Sales
JOIN Sales_Quota
ON Sales_Quota.BusinessEntityID = Sales.SalesPersonID
AND Sales_Quota.SalesQuotaYear = Sales.SalesYear
ORDER BY SalesPersonID, SalesYear;

 

着这段代码中,我将两个子查询转移到两个不同的CTEs中,第一个CTE用Sales来命名,定义了的第二个子查询,叫做SalesQuota在第一个CTE后面用逗号分隔与第二个。定义完成后,引用这两个别名来实现最终的select 语句,结果与之前复杂的代码结果完全相同。.

能够用一个单一的WITH 子句定义一个多重CTEs,然后包括这些CTEs在我的最中的TSQL语句中,这使得我可以更容易的读、开发和调试。使用多重CTEs对于复杂的TSQL逻辑而言,让我们将代码放到更容易管理的细小部分里面分隔管理。

CTE引用CTE

为了实现CTE引用另一个CTE我们需要满足下面两个条件:

  1. 被定义在同一个WITH自居中作为CTE被引用
  2. 被定义在被引用的CTE后面

代码如下:

USE AdventureWorks2012; GO WITH –第一个被重写的子查询CTE Sales AS ( SELECT SalesPersonID , SUM(TotalDue) AS TotalSales , YEAR(OrderDate) AS SalesYear FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID, YEAR(OrderDate) ), — 第二个子查询引用第一个CTETotalSales AS ( SELECT SUM(TotalSales)AS TotalSales , SalesYear FROM Sales GROUP BY SalesYear ) — 调用第二个CTE SELECT * FROM TotalSales ORDER BY SalesYear;

 

这个代码中,我定义了一个CTE命名为Sales ,被第二个CTE引用,定义第二个CTE叫做TotalSales,在这个CTE  中我汇总了TotalSales 列,通过组合SalesYear列。最后我使用Select语句引用第二个CTE。

CTE递归调用CTE实例

另一个CTE的主要功能就是调用自己,当用CTE调用自己的时候,就行程了CTE递归调用。一个递归CTE有两个主要部分,一个是锚成员,一个是递归成员。锚成员开启递归成员,这里你可以把锚成员查询当做一个没有引用CTE的查询。而递归成员将会引用CTE。这个锚成员确定了初始的记录集,然后递归成员来使用这个初始记录集。为了更好地理解递归CTE,我将创建一个实例数据通过使用递归CTE,

下面就是代码Listing 6:

 

USE tempdb; GO — 先创建一个用户表 CREATE TABLE dbo.Employee ( EmpID smallint NOT NULL, EmpName nvarchar(100) NOT NULL, Position nvarchar(50) NOT NULL, MgrId int NULL ); — 插入数据INSERT INTO dbo.Employee VALUES (1, N’Joe Steel’, N’President’,NULL) ,(2, N’John Smith’, N’VP Western Region Sales’,1) ,(3, N’Sue Jones’, N’VP Easter Region’,1) ,(4, N’Lynn Holland’, N’Sales Person’,2) ,(5, N’Linda Thomas’, N’Sales Person’,3 ) ,(6, N’Kathy Johnson’, N’Admin Assistant’,1) ,(7, N’Rich Little’, N’Sales Person’,3) ,(8, N’David Nelson’, N’Sales Person’, 2) ,(9, N’Mary Jackson’, N’Sales Person’, 3);

Listing 6

在Listing 6我创建了一个员工表,包含了员工信息,这个表中插入了9个不同的员工,MgrId 字段用来区分员工的领导的ID,这里有一个字段为null的记录。这个人没有领导且是这里的最高级领导。来看看我将如何使用递归CTE吧,在Listing7中:

USE tempdb; GO WITH ReportingStructure(MgrID, EmpID, EmpName, Position, OrgLevel) AS ( --锚部分 SELECT MgrID, EmpID, EmpName, Position, 0 AS OrgLevel FROM dbo.Employee WHERE MgrID IS NULL UNION ALL -- 递归部分 SELECT e.MgrID, e.EmpID, e.EmpName , e.Position, r.OrgLevel + 1 FROM dbo.Employee AS e INNER JOIN ReportingStructure AS r ON e.MgrID = r.EmpID ) SELECT MgrID, EmpID, EmpName, Position, OrgLevel FROM ReportingStructure;

Listing 7

执行脚本结果:

MgrID EmpID  EmpName        Position                   OrgLevel
----- ------ -------------- -------------------------- -----------
NULL  1      Joe Steel      President                  0
1     2      John Smith     VP Western Region Sales    1
1     3      Sue Jones      VP Easter Region           1
1     6      Kathy Johnson  Admin Assistant            1
2     4      Lynn Holland   Sales Person               2
2     8      David Nelson   Sales Person               2
3     5      Linda Thomas   Sales Person               2
3     7      Rich Little    Sales Person               2
3     9      Mary Jackson   Sales Person               2

我们能发现这个结果是所有员工分级结构,注意OrgLevel 字段确定了分层等级结构,当你看到0的时候说明这个人就是最大的领导了,每一个员工过的直属领导都比自己的OrgLevel 大1。

控制递归

有时候会出现无穷递归的CTE的可能,但是SQLServer有一个默认的最大递归值来避免出现无限循环的CTE递归。默认是100,下面我来举例说明:

USE tempdb; GO WITH InfiniteLoopCTE as ( — Anchor Part SELECT EmpID, MgrID, Position FROM dbo.Employee WHERE MgrID = 1 UNION ALL — Recursive Part SELECT InfiniteLoopCTE.EmpID , InfiniteLoopCTE.MgrID , InfiniteLoopCTE.Position FROM InfiniteLoopCTE JOIN dbo.Employee AS e ON e.EmpID = InfiniteLoopCTE.MgrID ) SELECT * FROM InfiniteLoopCTE;

Listing 8

这部分代码引发了一个无限循环,因为递归部分将永远返回多行数据,这部分查询返回的结果是MrgID 为1的结果。而我去运行这段代码后,只循环了100次,这就是由于最大递归次数的默认为100。当然这个值也是可以设定的。假如我们打算超过100次,150次的话,如下所示:

USE tempdb; GO –Creates an infinite loop WITH InfiniteLoopCTE as ( — 锚部分 SELECT EmpID, MgrID, Position FROM dbo.Employee WHERE MgrID = 1 UNION ALL — 递归部分 SELECT InfiniteLoopCTE.EmpID , InfiniteLoopCTE.MgrID , InfiniteLoopCTE.Position FROM InfiniteLoopCTE JOIN dbo.Employee AS e ON e.EmpID = InfiniteLoopCTE.MgrID ) SELECT * FROM InfiniteLoopCTE OPTION (MAXRECURSION 150);

Listing 9

通过设定MAXRECUSION 的值为150次实现了递归150次的最大递归限制,这个属性的最大值为32,767。

何时使用CTE

当然我们学习了如何使用CTE就要知道什么时候来使用它,下面三种情况是使用CTE简化你的T-SQL语句的情况:

  1. 查询中需要递归
  2. 查询中有多个子查询,或者你有重复的相同的子查询在单一语句中。
  3. 查询时复杂庞大的

总结

CTE的功能为SQLServer 提供了强大的补充,它让我们可以将复杂的代码切成很多易于管理和读取的小的代码段,同时还允许我们使用它来建立递归代码。CTE提供了另一种方法来实现复杂的T-SQL逻辑,为将来我们的开发提供了非常好的代码规范和易读性,

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/119733.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • c#的基本输入与输出

    c#的基本输入与输出

  • 《圣经》心得——孙继滨

    《圣经》心得——孙继滨

  • isNotBlank_participate用法介绍

    isNotBlank_participate用法介绍isNotEmpty将空格也作为参数,isNotBlank则排除空格参数StringUtils方法的操作对象是java.lang.String类型的对象,是JDK提供的String类型操作方法的补充,并且是null安全的(即如果输入参数String为null则不会抛出NullPointerException,而是做了相应处理,例如,如果输入为null则返回也是null等,具体可以查看源代码)。除了构造器,StringUtils中一共有130多个方法,并且都是static的,所以我们可以这样调用Str

  • MFC之进度条CProgressCtrl

    MFC之进度条CProgressCtrl一、成员函数简介1、create()针对不是通过资源文件上拖拉进度条控件生成的进度条,需要用此函数创建一个。2、SetRange()设置进度条的起始值和终止值。3、SetPos()设置进度条的当前位

  • win10修改视频默认播放器_win10无法更改默认播放器

    win10修改视频默认播放器_win10无法更改默认播放器你要用到WindowsPowerShell,它是win10系统自带的一个应用,要打开它,就单击开始菜单中的“所有应用”,然后找到WindowsPowerShell的文件夹,右键单击WindowsPowerShell(注意不是WindowsPowerShellISE),然后单击以管理员身份运行,就打开了。输入以下:get-appxpackage*zunevideo*|r

  • 一个简单完整的网页密码_简单的个人网页

    一个简单完整的网页密码_简单的个人网页获得源码链接,点击这里网页头部+banner和信息部分+新闻部分+底部一头部效果:先对css进行初始化分析:头部有一张图片和一个input输入框还有一个按钮+下面的通栏因为用到左浮,右浮的地方不同我们可以写一个通类这里的logo图片如果不定义宽高会影响下面的通栏的设置,影响其中的第一个为首的顺序无法对齐二、通栏(宽度为适应屏幕所以是10…

    2022年10月13日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号