在推出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逻辑,为将来我们的开发提供了非常好的代码规范和易读性,

T-SQL—理解CTEs的更多相关文章

  1. sql 理解视图

    可以看作是定义在sqlserver上的虚拟的表,本身并不存储数据,仅仅存储一个select语句和涉及的表的引用 通过视图,客户端不再需要知道底层表结构和其之间的关系,视图提供了一个统一访问数据的接口 ...

  2. [SQL] 理解SQL SERVER中的逻辑读,预读和物理读

    SQL SERVER数据存储的形式 在谈到几种不同的读取方式之前,首先要理解SQL SERVER数据存储的方式.SQL SERVER存储的最小单位为页(Page).每一页大小为8k,SQL SERVE ...

  3. sql 理解

    select b.*,  b.model_ent_name+cast(m.year as varchar)as modelname, m.index_value as val into #tb fro ...

  4. Java 程序员在写 SQL 时常犯的 10 个错误

    Java程序员编程时需要混合面向对象思维和一般命令式编程的方法,能否完美的将两者结合起来完全得依靠编程人员的水准: 技能(任何人都能容易学会命令式编程) 模式(有些人用“模式-模式”,举个例子,模式可 ...

  5. 十九、oracle pl/sql简介

    一.pl/sql 是什么pl/sql(procedural language/sql)是oracle在标准的sql语言上的扩展.pl/sql不仅允许嵌入sql语言,还可以定义变量和常量,允许使用条件语 ...

  6. oracle pl/sql 简介

    一.pl/sql 是什么pl/sql(procedural language/sql)是oracle在标准的sql语言上的扩展.pl/sql不仅允许嵌入sql语言,还可以定义变量和常量,允许使用条件语 ...

  7. Elasticsearch SQL

    es sql是一个X-pack组件 ,允许对es执行类似sql的查询,可以将Elasticsearch SQL理解为一个编译器,既能理解es,又能理解sql.可以通过利用es,实施大规模实时读取和处理 ...

  8. Java 程序员容易犯的10个SQL错误

    Java程序员编程时需要混合面向对象思维和一般命令式编程的方法,能否完美的将两者结合起来完全得依靠编程人员的水准: 技能(任何人都能容易学会命令式编程) 模式(有些人用“模式-模式”,举个例子,模式可 ...

  9. mybatis入门基础(七)----延迟加载

    一.什么是延迟加载 resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: ...

随机推荐

  1. 更改(修改)mysql自动增序列改变从1000开始

    更改(修改)mysql自动增序列改变从1000开始 ************************************************************************** ...

  2. 「Ionic」使用chrom時的跨域問題

    前言:在angularjs請求數據時,會遇到跨域問題,解決辦法有很多,但是都不是我想要的(很多人云亦云,都解決不了問題).如果你只是想在本機測試使用,可以參考如下設置.   具體辦法: 1.在电脑上新 ...

  3. jstl标签2

    我们前面讲的是ArrayList集合迭代,Hashmap/Hashset的情况下 举例: <h3>对map和set的迭代</h3> 放入字符串 <% //模拟 Map m ...

  4. 如何理解javascript closure ?

    接触过javascript的人应该听过闭包(closure),有一种观点认为是闭包赋予了javascript的强大能力,也赋予了它具备OOP的特征.既然javascript closure如此重要,那 ...

  5. 20款美化网站的 jQuery Lightbox 灯箱插件

    jQuery Lightbox 灯箱插件可以让你为您的网站和应用程序展示优雅的图像,视频 和其它内容(使用模式窗口).如果你是一个开发人员,你必须拥有 jQuery 灯箱插件集合,因为有一部分的客户会 ...

  6. Fixed Responsive Nav – 响应式的单页网站导航插件

    Fixed Responsive Nav 是一个响应式的,固定的,触摸友好的单页网站导航插件,响应式导航,流畅的动画滚动.该项目采用渐进增强构建,支持工作在 IE6 及以上版本的浏览器. 你可以给导航 ...

  7. [实现]Javascript代码的另一种压缩与加密方法——代码图片转换

    代码=图片 图片=代码 JS代码对于喜欢F12的同志来说,连个遮羞布都没有... 虽然把代码变成图片也仅仅只是增加一层纱布而已...但这方法还是挺好玩的,而且代码也被压缩了一点. 第一次看到[图片=代 ...

  8. jquery实现内容滚动

    HTML代码: <div class="scollNews"> <ul> <li><a href="#">1&l ...

  9. Objective-C instancetype关键字

     instancetype是clang 3.5开始,clang提供的一个关键字 表示某个方法返回的未知类型的Objective-C对象 instancetype会告诉编译器当前的类型,这点和NSObj ...

  10. SharePoint Online 创建门户网站系列之图片滚动

    前 言 创建SharePoint Online栏目我们之前已经介绍过了,具体就是内容编辑器方式.自带WebPart方式和JavaScript读取后台数据前台做展示的三种: 但是,对于复杂的展示来说,这 ...