次のような階層テーブルがあるとします。
CREATE TABLE [dbo].[btree]
(
id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);
ツリー構造全体を取得したいのですが。
たとえば、次のデータを使用します。
INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2, 1, '1.1 Group');
INSERT INTO [btree] VALUES (3, 1, '1.2 Group');
INSERT INTO [btree] VALUES (4, 2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5, 2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6, 3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7, 3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8, 4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9, 4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10, 5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11, 5, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (12, 6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13, 6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14, 7, '1.2.2.1 Items');
入手したい:
+----+-----------+---------------------+
| id | parent_id | description |
+----+-----------+---------------------+
| 1 | NULL | 1 Root |
| 2 | 1 | 1.1 Group |
| 4 | 2 | 1.1.1 Group |
| 8 | 4 | 1.1.1.1 Items |
| 9 | 4 | 1.1.1.2 Items |
| 5 | 2 | 1.1.2 Group |
| 10 | 5 | 1.1.2.1 Items |
| 11 | 5 | 1.1.2.2 Items |
| 3 | 1 | 1.2 Group |
| 6 | 3 | 1.2.1 Group |
| 12 | 6 | 1.2.1.1 Items |
| 13 | 6 | 1.2.1.2 Items |
| 7 | 3 | 1.2.2 Group |
| 14 | 7 | 1.2.2.1 Items |
+----+-----------+---------------------+
次のような再帰クエリを使用してレコードをフェッチしています。
;WITH tree AS
(
SELECT c1.id, c1.parent_id, c1.name, [level] = 1
FROM dbo.[btree] c1
WHERE c1.parent_id IS NULL
UNION ALL
SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1
FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.level, tree.id, parent_id, REPLICATE(' ', tree.level - 1) + tree.name AS description
FROM tree
OPTION (MAXRECURSION 0)
;
そしてこれが現在の結果です:
+----+-----------+---------------------+
| id | parent_id | description |
| 1 | NULL | 1 Root |
| 2 | 1 | 1.1 Group |
| 3 | 1 | 1.2 Group |
| 6 | 3 | 1.2.1 Group |
| 7 | 3 | 1.2.2 Group |
| 14 | 7 | 1.2.2.1 Items |
| 12 | 6 | 1.2.1.1 Items |
| 13 | 6 | 1.2.1.2 Items |
| 4 | 2 | 1.1.1 Group |
| 5 | 2 | 1.1.2 Group |
| 10 | 5 | 1.1.2.1 Items |
| 11 | 5 | 1.1.1.2 Items |
| 8 | 4 | 1.1.1.1 Items |
| 9 | 4 | 1.1.1.2 Items |
+----+-----------+---------------------+
レベルで並べ替える方法がわかりません。
各サブレベルにランクを設定する方法はありますか?
私は設定しました Rextester
「パス」フィールドを追加し、ファイルパスと同様に並べ替えます。 ypercubeが述べたように、この例では並べ替えは過度に単純化されており、たまたま機能しますが、簡単にするために、そのままにしておきます。ほとんどの場合、このパターンを使用するときは、とにかくIDではなく名前でソートします。
IF OBJECT_ID('[dbo].[btree]', 'U') IS NOT NULL
DROP TABLE [dbo].[btree];
GO
CREATE TABLE [dbo].[btree]
(
id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);
GO
INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2, 1, '1.1 Group');
INSERT INTO [btree] VALUES (3, 1, '1.2 Group');
INSERT INTO [btree] VALUES (4, 2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5, 2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6, 3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7, 3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8, 4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9, 4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10, 5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11, 5, '1.1.2.2 Items');
INSERT INTO [btree] VALUES (12, 6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13, 6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14, 7, '1.2.2.1 Items');
;WITH tree AS
(
SELECT c1.id, c1.parent_id, c1.name, [level] = 1, path = cast('root' as varchar(100))
FROM dbo.[btree] c1
WHERE c1.parent_id IS NULL
UNION ALL
SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1,
Path = Cast(tree.path+'/'+right('000000000' + cast(c2.id as varchar(10)),10) as varchar(100))
FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.path, tree.id, parent_id, REPLICATE(' ', tree.level - 1) + tree.name AS description
FROM tree
Order by path
OPTION (MAXRECURSION 0)
;
ここに 学期
不正行為、少しだけ;)ma、再帰なしを見てください!
rextester.com でテスト済み
SELECT btree.* -- , a,b,c,d -- uncomment to see the parts
FROM btree
OUTER APPLY
( SELECT rlc = REVERSE(LEFT(name, CHARINDEX(' ', name)-1))) AS r
OUTER APPLY
( SELECT a = CAST(REVERSE(PARSENAME(r.rlc, 1)) AS int),
b = CAST(REVERSE(PARSENAME(r.rlc, 2)) AS int),
c = CAST(REVERSE(PARSENAME(r.rlc, 3)) AS int),
d = CAST(REVERSE(PARSENAME(r.rlc, 4)) AS int)
) AS p
ORDER BY a, b, c, d ;
もちろん、上記はかなり制限されています。それは仮定の下でのみ機能します:
name
列には、実際の「パス」が(最初の部分に)格納されています。CAST .. AS int
は、パーツが数値の場合にのみ必要です。説明:コードは関数 PARSENAME()
を使用して機能します。これは、オブジェクト名を4つの部分に分割することを主な目的としています。
Server.Database.Schema.Object
| | | |
4th 3rd 2nd 1st
順序が逆になっていることに注意してください。例として、PARSENAME('dbo.btree', 2)
は結果として'dbo'
を提供します。3を指定すると、NULLが取得されます(そのため、REVERSE()
がコードで2回使用されます。それ以外の場合は、最初のヌル。'1.2'
は、null, null, 1, 2
が必要なときに、1, 2, null, null
に解析されます。
結論:結局のところ、ボブキャンベルの答えがより一般的であり、(結果の「パス」列に)パス階層を生成するので、それをORDER BY
に使用できるため、 。
あなたが検討する可能性のある他のオプション-テーブルのサイズが大きくなり、再帰的なソリューションが遅くなる場合-実際にパスを別の列に(順序付けに適した形式で、つまりパディングを使用して)保存するか、提供された HierarchyID
このユースケースにぴったりのタイプ、階層データ。