WITH RecursiveCTE (Col1, Col2, ..., Level) AS (
-- 1. 錨點成員 : 起始集合 (Level = 0)
SELECT Col1, Col2, ..., 0 AS Level
FROM BaseTable
WHERE
UNION ALL
-- 2. 遞歸成員 : 關聯上一層結果
SELECT t.Col1, t.Col2, ..., r.Level + 1
FROM BaseTable t
INNER JOIN RecursiveCTE r ON t.ParentID = r.ID -- 關鍵關聯條件
WHERE r.Level < @MaxDepth -- 可選:防止無限遞歸
)
-- 3. 外部查詢引用 CTE
SELECT * FROM RecursiveCTE
OPTION (MAXRECURSION 100); -- 預設 100,0 為無限 (慎用)
CREATE TABLE Category (
PKID INT IDENTITY PRIMARY KEY,
C_Name NVARCHAR(50) NOT NULL,
C_Level INT NOT NULL, -- 階層深度 (冗餘欄位,加速查詢)
C_Code NVARCHAR(255) NULL, -- 路徑編碼 '1/4/6/' (關鍵優化欄位)
C_Parent INT NOT NULL DEFAULT 0, -- 父節點 PKID
-- ... 其他欄位
);
-- 測試資料:1(根)-4(子)-6(孫)-9/10/11(曾孫) ; 2-5-8-12/13
WITH CatTree AS (
SELECT C_Name, PKID, C_Code, C_Parent, 0 AS Lvl
FROM Category WHERE PKID = 2 -- 從 '分類2' 開始
UNION ALL
SELECT c.C_Name, c.PKID, c.C_Code, c.C_Parent, p.Lvl + 1
FROM Category c
INNER JOIN CatTree p ON c.C_Parent = p.PKID
)
SELECT ct.C_Name AS NodeName,
p.C_Name AS ParentName,
ct.C_Code AS PathCode
FROM CatTree ct
LEFT JOIN Category p ON ct.C_Parent = p.PKID
OPTION (MAXRECURSION 0); -- 深度未知設 0
輸出:分類5(父:分類2) → 分類8(父:分類5) → 分類12/13(父:分類8)
錯誤示範:`OPTION (MAXRECURSION 2)` 若實際深度 > 2 會直接報錯終止 (Msg 530)。
正確做法:在 CTE 內部累加層級欄位,外層 `WHERE` 過濾。
WITH CatTree AS (
SELECT C_Name, PKID, C_Code, C_Parent, 0 AS Lvl
FROM Category WHERE PKID = 2
UNION ALL
SELECT c.C_Name, c.PKID, c.C_Code, c.C_Parent, p.Lvl + 1
FROM Category c
INNER JOIN CatTree p ON c.C_Parent = p.PKID
WHERE p.Lvl < 2 -- 在遞歸成員內部終止,不產生第 3 層資料
)
SELECT ct.C_Name, p.C_Name AS ParentName, ct.C_Code
FROM CatTree ct
LEFT JOIN Category p ON ct.C_Parent = p.PKID
WHERE ct.Lvl <= 2; -- 雙重保險
利用冗餘欄位 `C_Code` (如 `2/5/8/`) 儲存完整祖先路徑。新增/移動節點時由應用層/Trigger 維護此欄位。
-- 查詢 PKID=2 節點的所有後代 (含無限深度)
SELECT c.C_Name,
(SELECT TOP 1 C_Name FROM Category WHERE PKID = c.C_Parent) AS ParentName,
c.C_Code
FROM Category c
WHERE c.C_Code LIKE '2/%'; -- 索引範圍掃描,極快
-- 僅查前 3 層 (C_Level 冗餘欄位輔助)
SELECT c.C_Name, ...
FROM Category c
WHERE c.C_Code LIKE '2/%' AND c.C_Level <= 3;
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 遞歸 CTE | 標準 SQL、無冗餘欄位、邏輯清晰、支援動態深度 | 大樹 (萬節點+) 效能指數級下降 (Spool/遞歸迭代開銷) | 組織架構、BOM 爆炸、臨時分析、深度 < 100 層、節點 < 5000 |
| 路徑編碼 + LIKE | 單一 Index Seek/Range Scan、效能 O(log n)、支援覆蓋索引 | 寫入維護成本 (Trigger/應用層)、編碼長度限制 (900 bytes 索引鍵上限) | 高頻讀取、大規模樹 (十萬節點)、固定深度查詢、報表/即時 API |
| HierarchyID (SQL 2008+) | 原生型別、緊湊二進位、內建 `GetAncestor`/`IsDescendantOf`、索引支援 | 學習曲線、舊版不支援、字串轉換略繁瑣 | 新系統設計、標準化階層建模、需頻繁移動子樹 |
| Nested Sets (左/右值) | 單查詢取得整棵子樹 (BETWEEN) | 寫入極慢 (需重算全樹)、並發差 | 幾乎唯讀、靜態分類 (如電商類目) |
CTE 遞歸是 SQL Server 處理階層資料的利器,寫法優雅、維護簡單。但面對大規模、高頻讀取的樹狀結構,**路徑編碼法或 `HierarchyID` 是效能的終極答案**。架構設計時預留冗餘路徑欄位,查詢層將獲益無窮。