自己参照(階層)テーブルからツリー構造を取得できますか?


8

次のような階層テーブルがあるとします。

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

回答:


7

「パス」フィールドを追加し、ファイルパスと同様に並べ替えます。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)
;

ここで学期


それは正しいアイデアですが、パス式ではc2.id、すべての部分が同じ長さになるように、row_numberで置き換え、左側にパディングする必要があります。そうしないと、すべてのデータに対して機能しません。データだけで55と秩序の変化と2を交換
ypercubeᵀᴹ

完全に同意する。私はモバイルを利用していて、答えの競争に勝ちたいと思っていました:)実際、私はパスの「名前」フィールドを一般的に使用します。それは通常私のユースケースです。
ベンキャンベル

行番号についてはおそらく間違っています(必要ありません)が、パディングは間違っています。+1(row_numberを使用する場合、パスは名前の最初の部分を再構築します!)
ypercubeᵀᴹJan

Path小さな修正を加えて編集し、パディングを追加しました。
ypercubeᵀᴹ

1
最大深度に疑問がある場合は、通常、予想されるパスの長さの2倍を使用します。また、ID / row_numberの最大桁数がわかっている場合は、ゼロパディングを減らすことができます。
ベンキャンベル

4

不正行為、少しだけ;)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列には(最初の部分に)実際の「パス」が格納されています。
  • ツリーの深さは最大4です(したがって、パスには最大4つの部分があります)。
  • これCAST .. AS intは、パーツが数値の場合にのみ必要です。

説明:コードPARSENAME()は、オブジェクト名を4つの部分に分割することを主な目的とする関数を使用して機能します。

Server.Database.Schema.Object
  |        |       |      |
 4th      3rd     2nd    1st

順序が逆になっていることに注意してください。例として、PARSENAME('dbo.btree', 2)私たちを与える'dbo'result.With 3として、私たちはなぜだと(NULLを取得しますREVERSE()コードに2回使用されています。そうでなければ、我々は最初にヌルを取得したい。'1.2'に解析されるだろうnull, null, 1, 2私たちが望むながら1, 2, null, null。 )


結論:結局のところ、ボブキャンベルによる答えがより一般的であり、(結果の「パス」列に)パス階層を生成するので、それをに使用できるので、この方法を使用するとよいでしょうORDER BY

あなたが検討する可能性のある他のオプション-テーブルのサイズが大きくなり、再帰的なソリューションが遅くなる場合-実際にパスを別の列に(順序付けに適した形式で、つまりパディングを使用して)保存するか、提供されたHierarchyIDこのユースケースにぴったりのタイプ、階層データ。


:)それは本当に素晴らしいです!残念ながらnameこの場合は使用できません。解読するのに一晩かかりますが、説明はありますか?
McNets 2017年

それで、「名前」列には、例で提供したデータがありませんか?残念。
ypercubeᵀᴹ

いいえ、私はそれを例として使用しましたが、いくつかのレベルがあることに注意してください。
McNets 2017年

1
@Mcnetsは、nameパスが(テキストとともに)保存される(そうではない)場合'order173.palletA27.box9'.bag3Aでも、コードを使用できます(intへのキャストを削除するだけです)。いずれにしても、BenCambellによるクエリは、一般的な方法です。
ypercubeᵀᴹ

1
@EvanCarrollはい、hierarchyidタイプです。リンク付きの他のオプションに関する最後の段落を追加していました。
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.