今のMySQL 8.0サポートがクエリを再帰的に、我々はそれを言うことができるすべての一般的なSQLデータベースが再帰クエリをサポートする標準の構文で。
WITH RECURSIVE MyTree AS (
SELECT * FROM MyTable WHERE ParentId IS NULL
UNION ALL
SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;
私は、2017年のプレゼンテーションの再帰クエリスローダウンでMySQL 8.0の再帰クエリをテストしました。
以下は2008年からの私の元の答えです:
リレーショナルデータベースにツリー構造のデータを保存するには、いくつかの方法があります。あなたの例で示すものは2つの方法を使います:
- 隣接リスト(「親」列)および
- パスの列挙(名前の列にあるドット付きの番号)。
別のソリューションはNested Setsと呼ばれ、同じテーブルに格納することもできます。これらの設計の詳細については、Joe Celkoによる「SQL for Smartiesのツリーと階層」をお読みください。
私は通常、ツリー構造のデータを格納するために、クロージャーテーブル(別名「隣接関係」)と呼ばれる設計を好みます。別のテーブルが必要ですが、ツリーのクエリは非常に簡単です。
私は、SQLとPHPを使用したプレゼンテーションモデル「階層データのモデル」および私の著書「SQLアンチパターン:データベースプログラミングの落とし穴を回避する」で、クロージャテーブルについて説明します。
CREATE TABLE ClosureTable (
ancestor_id INT NOT NULL REFERENCES FlatTable(id),
descendant_id INT NOT NULL REFERENCES FlatTable(id),
PRIMARY KEY (ancestor_id, descendant_id)
);
あるノードから別のノードへの直接の祖先があるクロージャテーブルにすべてのパスを保存します。自身を参照する各ノードの行を含めます。たとえば、質問で示したデータセットを使用します。
INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
(1,1), (1,2), (1,4), (1,6),
(2,2), (2,4),
(3,3), (3,5),
(4,4),
(5,5),
(6,6);
これで、次のようにノード1から始まるツリーを取得できます。
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;
(MySQLクライアントでの)出力は次のようになります。
+----+
| id |
+----+
| 1 |
| 2 |
| 4 |
| 6 |
+----+
つまり、ノード3と5は、ノード1から降りていない別の階層の一部であるため、除外されます。
Re:直接の子供(または直接の親)に関するe-satisからのコメント。「path_length
」列をに追加しClosureTable
て、直接の子または親(またはその他の距離)を具体的に照会することを容易にすることができます。
INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
(1,1,0), (1,2,1), (1,4,2), (1,6,1),
(2,2,0), (2,4,1),
(3,3,0), (3,5,1),
(4,4,0),
(5,5,0),
(6,6,0);
次に、特定のノードの直接の子を照会するための用語を検索に追加できます。これらpath_length
は1の子孫です。
SELECT f.*
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
AND path_length = 1;
+----+
| id |
+----+
| 2 |
| 6 |
+----+
@ashrafからの再コメント:「ツリー全体を[名前]で並べ替えるのはどうですか?」
ノード1の子孫であるすべてのノードを返すクエリの例を次に示します。これらのノードをなどの他のノード属性を含むFlatTableに結合しname
、名前で並べ替えます。
SELECT f.name
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;
@Nateからのコメントの再投稿:
SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f
JOIN ClosureTable a ON (f.id = a.descendant_id)
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id)
WHERE a.ancestor_id = 1
GROUP BY a.descendant_id
ORDER BY f.name
+------------+-------------+
| name | breadcrumbs |
+------------+-------------+
| Node 1 | 1 |
| Node 1.1 | 1,2 |
| Node 1.1.1 | 1,2,4 |
| Node 1.2 | 1,6 |
+------------+-------------+
ユーザーが編集を今日提案しました。SOモデレーターは編集を承認しましたが、私はそれを取り消しています。
編集はORDER BY b.path_length, f.name
、おそらく順序が階層と一致することを確認するために、上記の最後のクエリのORDER BYがであるべきであることを示唆しました。しかし、「ノード1.2」の後に「ノード1.1.1」を注文するため、これは機能しません。
順序付けを適切な方法で階層と一致させたい場合、それは可能ですが、単純にパス長による順序付けではできません。たとえば、MySQL Closure Table階層型データベースへの私の回答-正しい順序で情報を取り出す方法を参照してください。