web-dev-qa-db-ja.com

SQLでノードのすべての子(再帰的)に関連付けられているすべてのエンティティをクエリする方法は?

この質問は、SQL全般に関するものです。特にMySQLに回答することは役立ちますが、必須ではありません。


わかりました、これを言葉で表すのに苦労しています...我慢してください。

私はもののツリー(ノードと呼びます)があり、次のようなテーブルがあるとします(構造は変更できます。これは単純なバージョンです)。

+---------+-----------+
| node_id | parent_id |
+---------+-----------+
| 1       | NULL      |
| 2       | NULL      |
| 3       | 1         |
| 4       | 1         |
| 5       | 5         |
| 7       | 2         |
| 8       | 5         |
+---------+-----------+

次に、エンティティのテーブルがあります。各エンティティは特定のノードに関連付けられています。たとえば(ここでも、構造を変更できます):

+-----------+---------+
| entity_id | node_id |
+-----------+---------+
| 1         | 1       |
| 2         | 1       |
| 3         | 4       |
| 4         | 7       |
| ...many more rows   |
+-----------+---------+

この構造は、多くのことを表すことができます。各エンティティは映画ですが、各ノードはジャンルです(ただし、サブジャンルのレベルは無制限にできます)。

したがって、特定のノードのすべてのエンティティを取得するのは簡単です。指定したnode_idのエンティティテーブルでクエリを実行するだけです。

これが私の質問です。特定のノードに関連付けられているすべてのエンティティをエンティティテーブルにどのようにクエリしますかandすべての子ノード(すべてのレベル、再帰的に)。映画の例では、特定のジャンルとそのすべてのサブジャンル、およびそのサブジャンルなどのすべての映画を検索します。

Wordを再帰的に説明しましたが、クエリが再帰的である必要があるという意味ではありませんが、概念的にはそうです。クエリは可能な限り高速である必要があります。テーブルの構造も変更する必要がある場合があります。

ご協力いただきありがとうございます!

2

行(5,5)はタイプミスだと思っているので、次のように使用しました。

create table tree (node_id int not null primary key, parent_id int);
insert into tree (node_id, parent_id) 
values (1,null),(2,(null),(3,1),(4,1),(5,4),(7,2),(8,5);

補足として、質問への回答、質問のアスキーアート、または上記のようなステートメントの作成と挿入を開始する場合は、どの表現を使用しますか?

ツリーを再帰的にトラバースするには、再帰的な共通テーブル式(CTE)を使用できます。

with rec (node_id, ancestor_id) as (
    select t.node_id, t.parent_id 
    from tree t 
    union all 
    select rec.node_id, t.parent_id 
    from rec, tree t 
    where rec.ancestor_id = t.node_id
) select * from rec

私は明示的な結合を好みますが、使用しているDBMSはCTEでそれらをサポートしていません。

with rec (node_id, ancestor_id) as (
    select t.node_id, t.parent_id 
    from tree t 
    union all 
    select rec.node_id, t.parent_id 
    from rec
    join tree t 
        on rec.ancestor_id = t.node_id
) select * from rec

今度は、この結果をエンティティテーブルに結合するだけです。

with rec (node_id, ancestor_id) as (
    select t.node_id, t.parent_id 
    from tree t 
    union all 
    select rec.node_id, t.parent_id 
    from rec
    join tree t 
        on rec.ancestor_id = t.node_id
) select rec.ancestor_id, e,entity_id
  from rec
  join entities e
      on e.node_id = rec.node_id

これは、DBMSバージョンが再帰的なCTEをサポートしていることを前提としています。 MySQL/MariaDBの最新バージョンがサポートしています。

もう1つの注意点として、ツリーの関係を正規化し、構造を別の関係に維持することができます。

create table treenode (node_id int not null primary key, ...)
create table tree (node_id int not null primary key
                  ,parent_id int not null);

ルートノードは、ツリーに存在しないノードです

古いバージョンを使用している場合、または大量のデータがあり、パフォーマンスを改善する必要がある場合は、モデルに情報を追加できます。最も一般的なものは次のとおりです。

  • ネストされたセット、すべてのノードには、それらが包含するすべてのノードの間隔があります
  • マテリアライズドパス、すべてのノードには祖先ノードを表す集約文字列があります
  • 推移閉包表、 tree にいくつかのメモといくつかの例があります。
2
Lennart