SQL Serverで再帰セルフ結合をマスターする: 最もシンプルな方法から高度なテクニックまで
SQL Server での再帰セルフ結合:最もシンプルな方法
最もシンプルな方法は、WITH句 を使用するものです。
WITH recursive cte AS (
SELECT
ID,
ParentID,
[Name]
FROM YourTable
WHERE ParentID IS NULL
UNION ALL
SELECT
c.ID,
c.ParentID,
t.[Name]
FROM cte AS c
JOIN YourTable AS t ON c.ID = t.ParentID
)
SELECT * FROM cte;
このクエリは、以下の処理を行います。
WITH recursive cte AS
で始まる部分で、CTE(Common Table Expression)を作成します。- 最初の
SELECT
ステートメントは、ParentID
がNULL
である行を選択します。つまり、最上位のノードを取得します。 UNION ALL
演算子は、CTE を再帰的に呼び出し、親IDがCTE内の既存の行のIDと一致する行を選択します。つまり、子ノードを取得します。- 最後の
SELECT
ステートメントは、CTE のすべての行を選択します。
この方法は、シンプルな構造の階層データに対して有効です。しかし、複雑な構造の場合は、パフォーマンスが低下する可能性があります。
より複雑な階層データの場合 は、CTE を使用せずに、再帰クエリを使用する方法もあります。
SELECT
ID,
ParentID,
[Name],
LEVEL() AS Level
FROM YourTable
WHERE ParentID IS NULL
ORDER BY ID ASC
DECLARE @depth INT = 0
WHILE @depth < (
SELECT MAX(LEVEL())
FROM YourTable
)
BEGIN
SELECT
ID,
ParentID,
[Name],
LEVEL() AS Level
FROM YourTable
WHERE ParentID IN (
SELECT ID
FROM YourTable
WHERE LEVEL() = @depth
)
ORDER BY ID ASC
SET @depth = @depth + 1
END
WHILE
ループは、CTE内の既存の行のID
をParentID
に持つ行を繰り返し取得します。DECLARE
変数@depth
は、現在のレベルをトラッキングします。
この方法は、より複雑な構造の階層データに対して有効ですが、記述が煩雑になる可能性があります。
適切な方法を選択
使用する方法は、データ構造と要件によって異なります。
- シンプルな構造の場合は、WITH句を使用した方法がおすすめです。
- 複雑な構造の場合は、再帰クエリを使用する必要があります。
- パフォーマンスが重要の場合は、どちらの方法でも、適切なインデックスを作成する必要があります。
サンプルコード:従業員データの階層構造を表示
テーブル構造
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY,
ManagerID INT,
Name VARCHAR(50)
);
従業員データ
INSERT INTO Employees (EmployeeID, ManagerID, Name)
VALUES
(1, NULL, 'John Doe'),
(2, 1, 'Jane Doe'),
(3, 1, 'Peter Jones'),
(4, 3, 'Mary Smith'),
(5, NULL, 'David Miller'),
(6, 5, 'Susan Brown');
CTE を使用した再帰セルフ結合
WITH recursive cte AS (
SELECT
EmployeeID,
ManagerID,
Name,
LEVEL() AS Level
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
SELECT
c.EmployeeID,
c.ManagerID,
t.Name,
LEVEL() AS Level
FROM cte AS c
JOIN Employees AS t ON c.EmployeeID = t.ManagerID
)
SELECT * FROM cte;
結果
EmployeeID | ManagerID | Name | Level
----------+-----------+------------+-------
1 | NULL | John Doe | 1
2 | 1 | Jane Doe | 2
3 | 1 | Peter Jones| 2
4 | 3 | Mary Smith | 3
5 | NULL | David Miller| 1
6 | 5 | Susan Brown| 2
説明
UNION ALL
演算子は、CTE を再帰的に呼び出し、ManagerID
がCTE内の既存の行のEmployeeID
と一致する行を選択します。つまり、子ノード (Jane Doe、Peter Jones、Mary Smith、Susan Brown) を取得します。LEVEL()
関数は、各行の階層レベルを計算します。
SELECT
EmployeeID,
ManagerID,
Name,
LEVEL() AS Level
FROM Employees
WHERE ManagerID IS NULL
ORDER BY ID ASC
DECLARE @depth INT = 0
WHILE @depth < (
SELECT MAX(LEVEL())
FROM Employees
)
BEGIN
SELECT
EmployeeID,
ManagerID,
Name,
LEVEL() AS Level
FROM Employees
WHERE ManagerID IN (
SELECT ID
FROM Employees
WHERE LEVEL() = @depth
)
ORDER BY ID ASC
SET @depth = @depth + 1
END
- ループ内の
SELECT
ステートメントは、現在のレベルのすべての従業員 (Jane Doe、Peter Jones、Mary Smith、Susan Brown) を取得します。
SQL Server での再帰セルフ結合:その他の方法
ヒエラルキー ID を使用する
この方法は、各行に階層レベルを表す HierarchyID
列を追加することで、再帰セルフ結合を簡略化する方法です。
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY,
ManagerID INT,
Name VARCHAR(50),
HierarchyID HIERARCHYID
);
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(1, NULL, 'John Doe', CONVERT(HIERARCHYID, '/1/'));
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(2, 1, 'Jane Doe', CONVERT(HIERARCHYID, '/1/2/'));
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(3, 1, 'Peter Jones', CONVERT(HIERARCHYID, '/1/3/'));
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(4, 3, 'Mary Smith', CONVERT(HIERARCHYID, '/1/3/4/'));
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(5, NULL, 'David Miller', CONVERT(HIERARCHYID, '/2/'));
INSERT INTO Employees (EmployeeID, ManagerID, Name, HierarchyID)
VALUES
(6, 5, 'Susan Brown', CONVERT(HIERARCHYID, '/2/3/'));
この後、以下のクエリを使用して、従業員データの階層構造を表示できます。
SELECT
EmployeeID,
ManagerID,
Name,
CONVERT(VARCHAR(MAX), HierarchyID) AS Path
FROM Employees
ORDER BY HierarchyID;
ツリー テーブルを使用する
この方法は、専用のツリー テーブルを使用して、階層データ構造を格納する方法です。
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY,
Name VARCHAR(50)
);
CREATE TABLE EmployeeTree (
ParentID INT,
ChildID INT,
PRIMARY KEY (ParentID, ChildID),
FOREIGN KEY (ParentID) REFERENCES Employees(EmployeeID),
FOREIGN KEY (ChildID) REFERENCES Employees(EmployeeID)
);
INSERT INTO Employees (EmployeeID, Name)
VALUES
(1, 'John Doe'),
(2, 'Jane Doe'),
(3, 'Peter Jones'),
(4, 'Mary Smith'),
(5, 'David Miller'),
(6, 'Susan Brown');
INSERT INTO EmployeeTree (ParentID, ChildID)
VALUES
(1, 2),
(1, 3),
(3, 4),
(5, 6);
WITH recursive cte AS (
SELECT
e.EmployeeID,
e.Name,
LEVEL() AS Level
FROM Employees AS e
WHERE e.EmployeeID IN (
SELECT ChildID
FROM EmployeeTree
WHERE ParentID IS NULL
)
UNION ALL
SELECT
et.ChildID,
e.Name,
LEVEL() AS Level
FROM EmployeeTree AS et
JOIN Employees AS e ON et.ChildID = e.EmployeeID
JOIN cte AS c ON et.ParentID = c.EmployeeID
)
SELECT * FROM cte;
再帰関数を使用する
CREATE FUNCTION GetEmployeeHierarchy(@EmployeeID INT)
RETURNS TABLE
AS
BEGIN
DECLARE @table TABLE (
EmployeeID INT,
Name VARCHAR(50),
Level INT
);
INSERT INTO @table
SELECT
e.EmployeeID,
e.Name,
1 AS Level
FROM Employees AS e
WHERE e.EmployeeID = @EmployeeID;
WITH recursive cte AS (
SELECT
EmployeeID,
Name,
Level
FROM @table
UNION ALL
SELECT
et.ChildID,
e.Name,
Level + 1
FROM EmployeeTree AS et
JOIN Employees AS e ON et.Child
sql sql-server recursion