SQL Server 2008 引入 hierarchyid CLR 使用者定義型別,專為樹狀結構(組織架構、目錄分類、BOM 物料清單等)設計。它以緊湊二進位編碼表示節點路徑,內建豐富方法,讓遞迴查詢變得簡潔高效。本文詳細說明建立、插入、查詢祖先/子孫、移動子樹等核心操作,並附完整方法速查表。
hierarchyid 值以 / 開頭與結尾,中間以數字與斜線表示層級路徑,如 /1/2/3/。/。CREATE TABLE dbo.Employees
(
OrgId HIERARCHYID NOT NULL,
EmployeeId INT NOT NULL,
Name VARCHAR(50) NOT NULL,
Title VARCHAR(50) NOT NULL,
CONSTRAINT PK_Employees PRIMARY KEY (EmployeeId),
CONSTRAINT UQ_OrgId UNIQUE (OrgId)
);
CREATE INDEX IX_Employees_OrgId ON dbo.Employees (OrgId); -- 支援範圍查詢
INSERT INTO dbo.Employees VALUES ('/', 10000, N'陳希章', N'CEO');
INSERT INTO dbo.Employees VALUES ('/1/', 10001, N'張三', N'CTO');
INSERT INTO dbo.Employees VALUES ('/2/', 10002, N'李四', N'CFO');
INSERT INTO dbo.Employees VALUES ('/1/1/', 10003, N'王五', N'IT Manager');
INSERT INTO dbo.Employees VALUES ('/1/2/', 10004, N'趙六', N'Manager');
INSERT INTO dbo.Employees VALUES ('/1/1/1/', 10005, N'洪七', N'Employee');
SELECT *, OrgId.GetLevel() AS Lvl FROM dbo.Employees;
DECLARE @Boss HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10000);
SELECT *, OrgId.GetLevel() AS Lvl
FROM dbo.Employees
WHERE OrgId.IsDescendantOf(@Boss) = 1;
DECLARE @Emp HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10004);
SELECT *, OrgId.GetLevel() AS Lvl
FROM dbo.Employees
WHERE @Emp.IsDescendantOf(OrgId) = 1;
SELECT @Emp.GetAncestor(1) AS ParentOrgId;
SELECT @Emp.GetRoot() AS RootOrgId;
-- 在 /1/1/ (IT Manager) 下新增子節點,置於 /1/1/1/ (洪七) 之後
DECLARE @Parent HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10003);
DECLARE @LastChild HIERARCHYID = (SELECT MAX(OrgId) FROM dbo.Employees WHERE OrgId.GetAncestor(1) = @Parent);
DECLARE @NewOrgId HIERARCHYID = @Parent.GetDescendant(@LastChild, NULL); -- NULL 表示附加在最後
INSERT INTO dbo.Employees VALUES (@NewOrgId, 10006, N'新員工', N'Junior');
-- 將趙六 (10004) 及其下屬從 CTO 移至 CFO
DECLARE @OldParent HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10001); -- CTO
DECLARE @NewParent HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10002); -- CFO
DECLARE @SubtreeRoot HIERARCHYID = (SELECT OrgId FROM dbo.Employees WHERE EmployeeId = 10004);
UPDATE dbo.Employees
SET OrgId = OrgId.GetReparentedValue(@OldParent, @NewParent)
WHERE OrgId.IsDescendantOf(@SubtreeRoot) = 1;
| 方法 | 說明 | 回傳型別 |
|---|---|---|
GetAncestor(n) | 取得第 n 層祖先 | hierarchyid |
GetDescendant(child1, child2) | 產生介於 child1 與 child2 之間的新子節點值 | hierarchyid |
GetLevel() | 取得節點深度(根=0) | INT |
GetRoot() | 取得根節點 | hierarchyid |
IsDescendantOf(parent) | 判斷是否為指定節點的子孫(含自身) | BIT |
Parse(string) | 將 /1/2/ 字串轉為 hierarchyid | hierarchyid |
ToString() | 轉為標準路徑字串 | NVARCHAR(4000) |
GetReparentedValue(old, new) | 將節點從 old 祖先路徑轉換至 new 祖先路徑(移動子樹核心) | hierarchyid |
Read/Write | 二進位序列化/反序列化(需 CLR,T-SQL 用 CAST/CONVERT) | BinaryReader/Writer |
hierarchyid 欄位建立一般 B-tree 索引即可支援 IsDescendantOf、GetAncestor 等範圍查詢。lft/rgt 巢狀集合欄位輔助報表查詢。hierarchyid 不支援 FILESTREAM、COLUMNSTORE 索引。若現有資料表採 ParentId 遞迴外鍵,可用遞迴 CTE 產生 hierarchyid 路徑並回寫:
WITH cte AS (
SELECT EmployeeId, CAST('/' AS HIERARCHYID) AS OrgId, 0 AS Lvl
FROM dbo.Employees WHERE ParentId IS NULL
UNION ALL
SELECT e.EmployeeId,
CAST(p.OrgId.ToString() + CAST(ROW_NUMBER() OVER (PARTITION BY p.EmployeeId ORDER BY e.EmployeeId) AS VARCHAR(10)) + '/' AS HIERARCHYID),
p.Lvl + 1
FROM dbo.Employees e JOIN cte p ON e.ParentId = p.EmployeeId
)
UPDATE e SET OrgId = c.OrgId FROM dbo.Employees e JOIN cte c ON e.EmployeeId = c.EmployeeId;
hierarchyid 讓階層式資料操作從「寫遞迴」變成「呼叫方法」,大幅降低開發複雜度與查詢成本,是 SQL Server 處理樹狀結構的首選工具。