SqlServer公用表表达式(CTE)的具体使用 |
SQL Server 中的公用表表达式(Common Table Expressions,简称 CTE)是一种临时命名的结果集,它在执行查询时存在,并且只在该查询执行期间有效 。CTE 类似于一个临时的视图或者一个内嵌的查询,但它提供了更好的可读性和重用性 。 CTE 使用 WITH 子句来定义,后面紧跟着一个或多个 CTE 的名称和定义(即 SELECT 语句) 。然后,在查询的主体中,你可以像引用表一样引用这些 CTE 。 1、本文内容
适用于:
指定临时命名的结果集,这些结果集称为公用表表达式 (CTE) 。 这派生自简单的查询,并在单个 SELECT、INSERT、UPDATE、DELETE 或 MERGE 语句的执行范围内定义 。 该子句也可用在 CREATE VIEW 语句中,作为该语句的 SELECT 定义语句的一部分 。 公用表表达式可以包括对自身的引用 。 这种表达式称为递归公用表表达式 。 参考官方地址: 2、语法[ WITH <common_table_expression> [ ,...n ] ] <common_table_expression>::= expression_name [ ( column_name [ ,...n ] ) ] AS ( CTE_query_definition ) 3、参数
4、创建和使用公用表表达式的准则下面的准则适用于非递归公用表表达式 。 有关适用于递归公用表表达式的准则,请参阅后面的定义和使用递归公用表表达式的准则 。 CTE 之后必须跟随引用部分或全部 CTE 列的单条 SELECT、INSERT、UPDATE 或 DELETE 语句 。 也可以在 CREATE VIEW 语句中将 CTE 指定为视图中 SELECT 定义语句的一部分 。 可以在非递归 CTE 中定义多个 CTE 查询定义 。 定义必须与以下集合运算符之一结合使用:UNION ALL、UNION、INTERSECT 或 EXCEPT 。 CTE 可以引用自身,也可以引用在同一 WITH 子句中预先定义的 CTE 。 不允许前向引用 。 不允许在一个 CTE 中指定多个 WITH 子句 。 例如,如果 CTE_query_definition 包含一个子查询,则该子查询不能包括定义另一个 CTE 的嵌套 WITH 子句 。 不能在 CTE_query_definition 中使用以下子句:
如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾 。 可以使用引用 CTE 的查询来定义游标 。 可以在 CTE 中引用远程服务器中的表 。 在执行 CTE 时,任何引用 CTE 的提示都可能与该 CTE 访问其基础表时发现的其他提示相冲突,这种冲突与引用查询中的视图的提示所发生的冲突相同 。 发生这种情况时,查询将返回错误 。 5、定义和使用递归公用表表达式的准则下面的准则适用于定义递归公用表表达式:
下面的准则适用于使用递归公用表表达式:
6、示例下载示例数据库AdventureWorks sample databases 6.1、下例显示每名销售代表每年的销售订单总数 。-- Define the CTE expression name and column list. WITH Sales_CTE (SalesPersonID, SalesOrderID, SalesYear) AS -- Define the CTE query. ( SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL ) -- Define the outer query referencing the CTE name. SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear FROM Sales_CTE GROUP BY SalesYear, SalesPersonID ORDER BY SalesPersonID, SalesYear; 以下为返回部分结果集
6.2、使用公用表表达式来限制次数和报告平均数以下示例显示销售代表在所有年度内的平均销售订单数 。 WITH Sales_CTE (SalesPersonID, NumberOfOrders) AS ( SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID ) SELECT AVG(NumberOfOrders) AS "Average Sales Per Person" FROM Sales_CTE; -- 返回结果 Average Sales Per Person ------------------------ 223 (1 行受影响) 6.3、在单个查询中使用多个 CTE 定义下面的示例显示如何在单个查询中定义多个 CTE 。 注意,其中使用逗号分隔 CTE 查询定义 。 SQL Server 2012 和更高版本中提供 FORMAT 函数,用于以货币格式显示货币金额 。 WITH Sales_CTE (SalesPersonID, TotalSales, SalesYear) AS -- Define the first CTE query. ( SELECT SalesPersonID, SUM(TotalDue) AS TotalSales, YEAR(OrderDate) AS SalesYear FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID, YEAR(OrderDate) ) , -- Use a comma to separate multiple CTE definitions. -- Define the second CTE query, which returns sales quota data by year for each sales person. Sales_Quota_CTE (BusinessEntityID, SalesQuota, SalesQuotaYear) AS ( SELECT BusinessEntityID, SUM(SalesQuota)AS SalesQuota, YEAR(QuotaDate) AS SalesQuotaYear FROM Sales.SalesPersonQuotaHistory GROUP BY BusinessEntityID, YEAR(QuotaDate) ) -- Define the outer query by referencing columns from both CTEs. SELECT SalesPersonID , SalesYear , FORMAT(TotalSales,'C','en-us') AS TotalSales , SalesQuotaYear , FORMAT (SalesQuota,'C','en-us') AS SalesQuota , FORMAT (TotalSales -SalesQuota, 'C','en-us') AS Amt_Above_or_Below_Quota FROM Sales_CTE JOIN Sales_Quota_CTE ON Sales_Quota_CTE.BusinessEntityID = Sales_CTE.SalesPersonID AND Sales_CTE.SalesYear = Sales_Quota_CTE.SalesQuotaYear ORDER BY SalesPersonID, SalesYear; 以下为部分结果集
6.4、使用递归公用表表达式显示递归的多个级别以下示例显示经理以及向经理报告的雇员的层次列表 。 该示例首先创建并填充 dbo.MyEmployees 表 。 -- Create an Employee table. CREATE TABLE dbo.T_Employees ( EmployeeID SMALLINT NOT NULL, FirstName NVARCHAR(30) NOT NULL, LastName NVARCHAR(40) NOT NULL, Title NVARCHAR(50) NOT NULL, DeptID SMALLINT NOT NULL, ManagerID SMALLINT NULL, CONSTRAINT PK_T_EmployeeID PRIMARY KEY CLUSTERED (EmployeeID ASC) ); -- Populate the table with values. INSERT INTO dbo.T_Employees VALUES (1, N'Ken', N'Sánchez', N'Chief Executive Officer',16, NULL) ,(273, N'Brian', N'Welcker', N'Vice President of Sales', 3, 1) ,(274, N'Stephen', N'Jiang', N'North American Sales Manager', 3, 273) ,(275, N'Michael', N'Blythe', N'Sales Representative', 3, 274) ,(276, N'Linda', N'Mitchell', N'Sales Representative', 3, 274) ,(285, N'Syed', N'Abbas', N'Pacific Sales Manager', 3, 273) ,(286, N'Lynn', N'Tsoflias', N'Sales Representative', 3, 285) ,(16, N'David', N'Bradley', N'Marketing Manager', 4, 273) ,(23, N'Mary', N'Gibson', N'Marketing Specialist', 4, 16); WITH DirectReports(ManagerID, EmployeeID, Title, EmployeeLevel) AS ( SELECT ManagerID, EmployeeID, Title, 0 AS EmployeeLevel FROM dbo.T_Employees WHERE ManagerID IS NULL UNION ALL SELECT e.ManagerID, e.EmployeeID, e.Title, EmployeeLevel + 1 FROM dbo.T_Employees AS e INNER JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID ) SELECT ManagerID, EmployeeID, Title, EmployeeLevel FROM DirectReports ORDER BY ManagerID; 返回结果集
6.5、使用递归公用表表达式显示递归的两个级别以下示例显示经理以及向经理报告的雇员 。 将返回的级别数目限制为两个 。 WITH DirectReports(ManagerID, EmployeeID, Title, EmployeeLevel) AS ( SELECT ManagerID, EmployeeID, Title, 0 AS EmployeeLevel FROM dbo.T_Employees WHERE ManagerID IS NULL UNION ALL SELECT e.ManagerID, e.EmployeeID, e.Title, EmployeeLevel + 1 FROM dbo.T_Employees AS e INNER JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID ) SELECT ManagerID, EmployeeID, Title, EmployeeLevel FROM DirectReports WHERE EmployeeLevel <= 2 ; 返回结果集
6.6、使用递归公用表表达式显示层次列表以下示例添加了经理和员工的姓名,以及他们各自的头衔 。 通过缩进各个级别,突出显示经理和雇员的层次结构 。 WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort) AS (SELECT CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName), e.Title, e.EmployeeID, 1, CONVERT(VARCHAR(255), e.FirstName + ' ' + e.LastName) FROM dbo.T_Employees AS e WHERE e.ManagerID IS NULL UNION ALL SELECT CONVERT(VARCHAR(255), REPLICATE ('| ' , EmployeeLevel) +e.FirstName + ' ' + e.LastName), e.Title, e.EmployeeID, EmployeeLevel + 1, CONVERT (VARCHAR(255), RTRIM(Sort) + '| ' + FirstName + ' ' + LastName) FROM dbo.T_Employees AS e JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID ) SELECT EmployeeID, Name, Title, EmployeeLevel FROM DirectReports ORDER BY Sort; 返回结果集
在这个递归 CTE 的例子中,我们首先选择所有的顶级经理(锚点成员),然后递归地选择每个经理的直接下属,同时跟踪他们在层级结构中的位置(通过 Level 列) 。 6.7、使用 MAXRECURSION 取消一条语句可以使用 MAXRECURSION 来防止不合理的递归 CTE 进入无限循环 。 下面的示例特意创建了一个无限循环,然后使用 MAXRECURSION 提示将递归级别限制为两级 。 --Creates an infinite loop WITH cte (EmployeeID, ManagerID, Title) AS ( SELECT EmployeeID, ManagerID, Title FROM dbo.T_Employees WHERE ManagerID IS NOT NULL UNION ALL SELECT cte.EmployeeID, cte.ManagerID, cte.Title FROM cte JOIN dbo.T_Employees AS e ON cte.ManagerID = e.EmployeeID ) --Uses MAXRECURSION to limit the recursive levels to 2 SELECT EmployeeID, ManagerID, Title FROM cte OPTION (MAXRECURSION 2); 返回结果集,并由错误提示
在更正代码错误之后,就不再需要 MAXRECURSION 。 以下示例显示了更正后的代码 。 WITH cte (EmployeeID, ManagerID, Title) AS ( SELECT EmployeeID, ManagerID, Title FROM dbo.T_Employees WHERE ManagerID IS NOT NULL UNION ALL SELECT e.EmployeeID, e.ManagerID, e.Title FROM dbo.T_Employees AS e INNER JOIN cte ON e.ManagerID = cte.EmployeeID ) SELECT EmployeeID, ManagerID, Title FROM cte;
6.8、使用公用表表达式来有选择地执行 SELECT 语句中的递归关系操作以下示例显示了为 ProductAssemblyID = 800 生产自行车所需的产品装配和部件层次结构 。 USE AdventureWorks2022; GO WITH Parts(AssemblyID, ComponentID, PerAssemblyQty, EndDate, ComponentLevel) AS ( SELECT b.ProductAssemblyID, b.ComponentID, b.PerAssemblyQty, b.EndDate, 0 AS ComponentLevel FROM Production.BillOfMaterials AS b WHERE b.ProductAssemblyID = 800 AND b.EndDate IS NULL UNION ALL SELECT bom.ProductAssemblyID, bom.ComponentID, p.PerAssemblyQty, bom.EndDate, ComponentLevel + 1 FROM Production.BillOfMaterials AS bom INNER JOIN Parts AS p ON bom.ProductAssemblyID = p.ComponentID AND bom.EndDate IS NULL ) SELECT AssemblyID, ComponentID, Name, PerAssemblyQty, EndDate,ComponentLevel FROM Parts AS p INNER JOIN Production.Product AS pr ON p.ComponentID = pr.ProductID ORDER BY ComponentLevel, AssemblyID, ComponentID; 返回结果集
6.9、在 UPDATE 语句中使用递归 CTE下例更新用于生成产品‘Road-550-W Yellow, 44’ (ProductAssemblyID``800 的所有部件的 PerAssemblyQty 值 。 公用表表达式将返回用于生成 ProductAssemblyID 800 的部件和用于生成这些部件的组件等的层次结构列表 。 只修改公用表表达式所返回的行 。 USE AdventureWorks2022; GO WITH Parts(AssemblyID, ComponentID, PerAssemblyQty, EndDate, ComponentLevel) AS ( SELECT b.ProductAssemblyID, b.ComponentID, b.PerAssemblyQty, b.EndDate, 0 AS ComponentLevel FROM Production.BillOfMaterials AS b WHERE b.ProductAssemblyID = 800 AND b.EndDate IS NULL UNION ALL SELECT bom.ProductAssemblyID, bom.ComponentID, p.PerAssemblyQty, bom.EndDate, ComponentLevel + 1 FROM Production.BillOfMaterials AS bom INNER JOIN Parts AS p ON bom.ProductAssemblyID = p.ComponentID AND bom.EndDate IS NULL ) UPDATE Production.BillOfMaterials SET PerAssemblyQty = c.PerAssemblyQty * 2 FROM Production.BillOfMaterials AS c INNER JOIN Parts AS d ON c.ProductAssemblyID = d.AssemblyID WHERE d.ComponentLevel = 0; 6.10、使用多个定位点和递归成员以下示例使用多个定位点和递归成员来返回指定的人的所有祖先 。 创建了一个表,并在表中插入值,以建立由递归 CTE 返回的宗谱 。 -- Genealogy table IF OBJECT_ID('dbo.T_Person','U') IS NOT NULL DROP TABLE dbo.T_Person; GO CREATE TABLE dbo.T_Person(ID int, Name VARCHAR(30), Mother INT, Father INT); GO INSERT dbo.T_Person VALUES (1, 'Sue', NULL, NULL) ,(2, 'Ed', NULL, NULL) ,(3, 'Emma', 1, 2) ,(4, 'Jack', 1, 2) ,(5, 'Jane', NULL, NULL) ,(6, 'Bonnie', 5, 4) ,(7, 'Bill', 5, 4); GO -- Create the recursive CTE to find all of Bonnie's ancestors. WITH Generation (ID) AS ( -- First anchor member returns Bonnie's mother. SELECT Mother FROM dbo.T_Person WHERE Name = 'Bonnie' UNION -- Second anchor member returns Bonnie's father. SELECT Father FROM dbo.T_Person WHERE Name = 'Bonnie' UNION ALL -- First recursive member returns male ancestors of the previous generation. SELECT T1.Father FROM Generation AS T2 INNER JOIN dbo.T_Person AS T1 ON T2.ID=T1.ID UNION ALL -- Second recursive member returns female ancestors of the previous generation. SELECT T1.Mother FROM Generation AS T2 INNER JOIN dbo.T_Person AS T1 ON T2.ID=T1.ID ) SELECT T1.ID,T1.Name,T1.Mother, T1.Father FROM Generation AS T2 INNER JOIN dbo.T_Person AS T1 ON T2.ID = T1.ID; GO 返回结果集
到此这篇关于SqlServer公用表表达式(CTE)的具体使用的文章就介绍到这了,更多相关Sql 公用表表达式内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持! |