SQL Server 2005引進了一個很有價值的新的Transact-SQL語言組件:一個通用表表達式(Common Table Expression,CTE),它是派生表和視圖的一個便捷的替代。通過使用CTE,我們可以創建一個命名結果集來在SELECT、INSERT、UPDATE和DELETE語句中引用,而無須保存結果集結構的任何元數據。在本文中,我將闡述如何在SQL Server 2005中創建CTE——包括如何使用CTE來創建一個遞歸查詢——並舉幾個例子來說明它們是如何使用的。注意,本文中所有例子都使用SQL Server 2005的AdventureWorks示例數據庫。
在SQL Server 2005中創建一個基本CTE
我們可以在SELECT、INSERT、UPDATE或DELETE語句之前添加一個WITH子句來構成一個CTE。下面的語法顯示了WITH子句的基本構造和CTE定義:
[WITH <CTE_definition> [,...n]] <SELECT, INSERT, UPDATE, or DELETE statement that calls the CTEs> <CTE_definition>::= CTE_name [(column_name [,...n ])] AS ( CTE_query ) |
如上面語句所示,你可以在可選的WITH子句中定義多個CTE。CTE定義包含CTE名稱、CTE字段名稱、AS關鍵字和括號中的CTE查詢。注意,CTE字段名稱的數目必須與CTE查詢返回的字段數目相匹配。另外,如果CTE查詢提供所有字段名稱,那麼字段名稱是可選的。
現在我們已經對SQL Server的CTE語法有了基本的瞭解,下面讓我們來看一個CTE定義的例子,以便更好地理解這個語法。下面的例子定義了一個命名爲ProductSold 的CTE,接着在SELECT語句中引用了CTE:
WITH ProductSold (ProductID, TotalSold) AS ( SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail GROUP BY ProductID ) SELECT p.ProductID, p.Name, p.ProductNumber, ps.TotalSold FROM Production.Product AS p INNER JOIN ProductSold AS ps ON p.ProductID = ps.ProductID |
這裏可以看到,WITH子句在引用CTE的SELECT語句之前。WITH子句的第一行包含了CTE的名稱(ProductSold)以及在CTE 的兩個字段名稱(ProductID 和TotalSold)。接着是AS關鍵字,緊接着是括號中的CTE查詢。這樣,CTE查詢返回了每個產品的銷售總數。
當Product表與CTE聯接時,WITH子句後面的SELECT語句根據ProductID,引用了CTE的名稱。這個語句就像調用一個表格或視圖一樣調用CTE。但是,與表格與視圖不同的是,CTE只對WITH子句的語句有效。如果在一個後續語句中引用CTE——而沒有重新定義CTE——就會出錯。
使用通用表表達式的其中一個優點是你可以在調用語句中多次引用CTE。比如,下面的語句定義了一個命名爲Employees的CTE,接着在跟在WITH子句的SELECT語句中兩次調用CTE:
WITH Employees (EmpID, MgrID, FName, LName, Email) AS ( SELECT e.EmployeeID, e. ManagerID, c.FirstName, c.LastName, c.EmailAddress FROM HumanResources.Employee e INNER JOIN Person.Contact c ON e.ContactID = c.ContactID ) SELECT e.FName + ' ' + e.LName AS EmpName, e.Email AS EmpEmail, m.FName + ' ' + m.LName AS MgrName, m.Email AS MgrEmail FROM Employees e LEFT OUTER JOIN Employees m ON e.MgrID = m.EmpID |
在這個例子中,SQL Server CTE查詢返回一個員工ID、姓名和郵件地址以及他們的經理ID的列表。然後,跟在WITH子句後的SELECT語句將與CTE聯接來返回經理的姓名和郵件地址。你可以通過使用派生表(子查詢)來獲得相同的結果,但是這就意味着需要多次重複相同的子查詢,同時也使代碼更加複雜。
在WITH子句中創建多個CTE
在CTE語法中可以看到,我們可以在WITH子句中定義多個CTE,然後在接下來的語句中按照需要多次調用這些CTE。下面的例子說明了這是如何實現的。下面的WITH子句包含了兩個CTE定義:
WITH Cost (ProductID, AvgCost) AS ( SELECT ProductID, AVG(StandardCost) FROM Production.ProductCostHistory GROUP BY ProductID ), Sold (ProductID, AvgSold) AS ( SELECT ProductID, AVG(OrderQty) FROM Sales.SalesOrderDetail GROUP BY ProductID ) SELECT p.ProductID, p.Name, (AvgCost * AvgSold)AS TotalCost FROM Sold s INNER JOIN Production.Product p ON s.ProductID = p.ProductID INNER JOIN Cost c ON p.ProductID = c.ProductID |
創建一個遞歸通用表表達式
SQL Server中CTE最有價值的功能是創建遞歸查詢的功能——一種反覆自身引用以返回數據子集的查詢類型。遞歸查詢最常用於返回層次式數據。比如,在AdventureWorks數據庫中的Employee表包含了每個員工的經理ID。事實上,經理ID是該經理管理的員工ID。因此, Employee表格包含了從CEO往下的整個管理層次報告結構。
可以通過創建一個CTE查詢來定義一個CTE來檢索這個分層結構,其中這個查詢使用一個UNION ALL、UNION、INTERSECT或EXCEPT操作符來聯接多個SELECT語句。下面讓我們來看個例子。
在這個WITH子句中,CTE查詢包含兩個用UNION ALL操作符聯接的SELECT語句:
WITH Reports (EmpLevel, EmpID, ContactID, MgrID) AS ( SELECT 1, EmployeeID, ContactID, ManagerID FROM HumanResources.Employee WHERE ManagerID IS NULL UNION ALL SELECT r.EmpLevel + 1, e.EmployeeID, e.ContactID, e.ManagerID FROM HumanResources.Employee e INNER JOIN Reports r ON e.ManagerID = r.EmpID ) SELECT r.EmpLevel, r.EmpID, c.FirstName + ' ' + c.LastName AS EmpName, MgrID FROM Reports r INNER JOIN Person.Contact c ON r.ContactID = c.ContactID ORDER BY r.EmpLevel, r.MgrID, r.EmpID |
在CTE查詢中的第一個SELECT語句僅僅檢索頂層員工——CEO。爲了檢索到頂層員工,可以使用一個指定ManagerID值爲NULL的WHERE子句實現。
換言之,這個人選並不直接向另一個經理報告。注意:SELECT語句中的第一個字段是1。它用於標識這個查詢返回的員工是在最高層,第一層。
第二個SELECT語句(在UNION ALL 操作符之後)基於經理和員工ID將Employee表格與Reports CTE聯接。通過這種方式的自引用,SQL Server自動將其作爲遞歸查詢並按照需要重複檢索以返回每個層次的員工。每次查詢運行時,第一個字段值設爲1,然後每層都遞增1。
在WITH子句後的SELECT語句將Reports CTE與Contact表聯接來檢索員工的姓名。下面的查詢結果顯示了這個語句返回的數據樣本:
EmpLevel |
EmpID |
EmpName |
MgrID |
1 |
109 |
Ken Sanchez |
NULL |
2 |
6 |
David Bradley |
109 |
2 |
12 |
Terri Duffy |
109 |
2 |
42 |
Jean Trenary |
109 |
2 |
140 |
Laura Norman |
109 |
2 |
148 |
James Hamilton |
109 |
2 |
273 |
Brian Welcker |
109 |
3 |
2 |
Kevin Brown |
6 |
3 |
46 |
Sariya Harnpadoungsataya |
6 |
3 |
106 |
Mary Gibson |
6 |
3 |
119 |
Jill Williams |
6 |
3 |
203 |
Terry Eminhizer |
6 |
3 |
269 |
Wanida Benshoof |
6 |
3 |
271 |
John Wood |
6 |
3 |
272 |
Mary Dempsey |
6 |
3 |
3 |
Roberto Tamburello |
12 |
3 |
66 |
Janaina Bueno |
42 |
3 |
102 |
Dan Bacon |
42 |
3 |
117 |
François Ajenstat |
42 |
3 |
128 |
Dan Wilson |
42 |
3 |
149 |
Ramesh Meyyappan |
42 |
3 |
150 |
Stephanie Conroy |
42 |
3 |
176 |
Karen Berg |
42 |
3 |
30 |
Paula Barreto de Mattos |
140 |
3 |
71 |
Wendy Kahn |
140 |
3 |
103 |
David Barber |
140 |
3 |
139 |
David Liu |
140 |
3 |
21 |
Peter Krebs |
148 |
3 |
44 |
A. Scott Wright |
148 |
3 |
200 |
Hazem Abolrous |
148 |
3 |
218 |
Gary Altman |
148 |
3 |
268 |
Stephen Jiang |
273 |
3 |
284 |
Amy Alberts |
273 |
3 |
288 |
Syed Abbas |
273 |
4 |
4 |
Rob Walters |
3 |
4 |
9 |
Gail Erickson |
3 |
4 |
11 |
Jossef Goldberg |
3 |
結果是根據員工的級別排列的。注意,第二行到第七行顯示的是MgrID值爲109的數據,它是第一行顯示的頂層員工的ID。下面的行反映了相同分層的數據。
遞歸CTE,與SQL Server中的其它通用表表達式一樣,提供了強大的數據檢索功能。與視圖不同的是,它並不需要保存元數據。與派生表不同的是,它並不需要重複不必要的代碼。CTE可以將代碼分成不相關的單元,這有助於簡化代碼複雜性。對於遞歸查詢,CTE則更加好用。剛開始使用CTE時,你可能必須花點時間來適應它們,但是一旦熟悉了,那麼你將樂在其中。