SQL 邏輯處理順序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。SELECT 別名在 GROUP BY 時尚不存在,導致以下寫法報錯:
-- 錯誤:OrderYear 在 GROUP BY 時尚未產生
SELECT YEAR(OrderDate) AS OrderYear, COUNT(DISTINCT CustomerID) AS NumCusts
FROM dbo.Orders GROUP BY OrderYear;
CTE(Common Table Expression)讓我們「先定義結果集別名,再主查詢引用」,完美解決此問題,同時提升可讀性與維護性。
WITH cte_name (column_list) AS (
cte_query_definition
)
outer_query_referring_to_cte_name;
WITH 關鍵字,故 CTE 陳述式**前一行必須以分號結尾**(或批次第一條)。WITH C(OrderYear, CustomerID) AS (
SELECT YEAR(OrderDate), CustomerID FROM dbo.Orders
)
SELECT OrderYear, COUNT(DISTINCT CustomerID) AS NumCusts
FROM C GROUP BY OrderYear;
WITH C1 AS (
SELECT YEAR(OrderDate) AS OrderYear, CustomerID FROM dbo.Orders
),
C2 AS (
SELECT OrderYear, COUNT(DISTINCT CustomerID) AS NumCusts
FROM C1 GROUP BY OrderYear
)
SELECT OrderYear, NumCusts FROM C2 WHERE NumCusts > 70;
WITH YearlyCount AS (
SELECT YEAR(OrderDate) AS OrderYear, COUNT(DISTINCT CustomerID) AS NumCusts
FROM dbo.Orders GROUP BY YEAR(OrderDate)
)
SELECT Cur.OrderYear,
Cur.NumCusts AS CurNumCusts,
Prv.NumCusts AS PrvNumCusts,
Cur.NumCusts - Prv.NumCusts AS Growth
FROM YearlyCount Cur
LEFT JOIN YearlyCount Prv ON Cur.OrderYear = Prv.OrderYear + 1;
同一 CTE 在外層查詢被引用多次,邏輯清晰、避免重複撰寫。
-- 4.1 交叉產生測試資料並寫入新表
WITH CrossCustomers AS (
SELECT 1 AS c, C1.* FROM dbo.Customers C1, dbo.Customers C2
)
SELECT ROW_NUMBER() OVER (ORDER BY c) AS KeyCol,
CustomerID, CompanyName, ContactName, ContactTitle,
Address, City, Region, PostalCode, Country, Phone, Fax
INTO dbo.CustomersDups FROM CrossCustomers;
-- 4.2 刪除重複,保留每 CustomerID 最大 KeyCol
WITH JustDups AS (
SELECT * FROM dbo.CustomersDups C1
WHERE KeyCol < (SELECT MAX(KeyCol) FROM dbo.CustomersDups C2
WHERE C2.CustomerID = C1.CustomerID)
)
DELETE FROM JustDups;
CTE 不支援直接巢狀,但可封裝於 View / Inline TVF 中,再由外層 CTE 引用,實現邏輯分層。
CREATE VIEW dbo.VYearCnt AS
WITH YearCnt AS (
SELECT YEAR(OrderDate) AS OrderYear, COUNT(DISTINCT CustomerID) AS NumCusts
FROM dbo.Orders GROUP BY YEAR(OrderDate)
)
SELECT * FROM YearCnt;
作者指出:在 View/UDF 中直接使用 CTE 價值有限,因本身已是模組化單元。
經典案例:給定員工 ID,遞迴查詢其所有下屬。
-- 最佳索引:覆蓋索引避免 Key Lookup
CREATE UNIQUE INDEX idx_mgr_emp ON dbo.Employees(ReportsTo, EmployeeID)
INCLUDE (FirstName, LastName);
-- 遞迴查詢
WITH EmpsCTE AS (
-- Anchor member:根節點
SELECT EmployeeID, ReportsTo, FirstName, LastName
FROM dbo.Employees WHERE EmployeeID = 5
UNION ALL
-- Recursive member:下一層
SELECT E.EmployeeID, E.ReportsTo, E.FirstName, E.LastName
FROM EmpsCTE MGR
JOIN dbo.Employees E ON E.ReportsTo = MGR.EmployeeID
)
SELECT * FROM EmpsCTE
OPTION (MAXRECURSION 0); -- 0 = 無限制,預設 100
關鍵點:
MAXRECURSION 防止無限迴圈,生產環境建議設定合理上限。INCLUDE 欄位建立覆蓋索引,避免書籤查找,大幅提升遞迴效能。DISTINCT、GROUP BY、TOP 等彙總運算子。WITH 前補分號。CTE 是 SQL Server 2005 最具生產力的 T-SQL 增強之一:替代衍生表、簡化遞迴、支援多重引用、提升程式碼模組化。善用顯式欄位清單與遞迴上限控制,能寫出既高效又易維護的查詢。