sql >> データベース >  >> RDS >> Mysql

クロージャテーブルの階層データ構造でのサブツリーの並べ替え

    この質問は、クロージャテーブルだけでなく、階層データを格納する他の方法でも頻繁に発生します。どのデザインでも簡単ではありません。

    私がClosureTableについて思いついた解決策には、1つの追加の結合が含まれます。ツリー内のすべてのノードは、「パンくずリスト」タイプのクエリのように、その祖先のチェーンに参加します。次に、GROUP_CONCAT()を使用して、ブレッドクラムをコンマ区切りの文字列に折りたたみ、ツリーの深さでID番号を並べ替えます。これで、並べ替えることができる文字列ができました。

    SELECT c2.*, cc2.ancestor AS `_parent`,
      GROUP_CONCAT(breadcrumb.ancestor ORDER BY breadcrumb.depth DESC) AS breadcrumbs
    FROM category AS c1
    JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
    JOIN category AS c2 ON (cc1.descendant = c2.id)
    LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
    JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
    WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
    GROUP BY cc1.descendant
    ORDER BY breadcrumbs;
    
    +----+------------+--------+---------+-------------+
    | id | name       | active | _parent | breadcrumbs |
    +----+------------+--------+---------+-------------+
    |  1 | Cat 1      |      1 |    NULL | 1           |
    |  3 | Cat  1.1   |      1 |       1 | 1,3         |
    |  4 | Cat  1.1.1 |      1 |       3 | 1,3,4       |
    |  7 | Cat 1.1.2  |      1 |       3 | 1,3,7       |
    |  6 | Cat 1.2    |      1 |       1 | 1,6         |
    +----+------------+--------+---------+-------------+
    

    警告:

    • 「1,3」と「1,6」と「1,327」を並べ替えると、意図した順序にならない可能性があるため、id値の長さは均一である必要があります。ただし、「001,003」と「001,006」と「001,327」を並べ替えると、したがって、id値を1000000+から開始するか、ZEROFILLを使用する必要があります。 category_closureテーブルの祖先と子孫の場合。
    • このソリューションでは、表示順序はカテゴリIDの番号順に依存します。 id値の番号順は、ツリーを表示する順序を表していない場合があります。または、数値ID値に関係なく、表示順序を自由に変更したい場合があります。または、同じカテゴリデータを複数のツリーに表示し、それぞれの表示順序を変えたい場合もあります。
      より自由度が必要な場合は、IDとは別に並べ替え順序の値を保存する必要があり、ソリューションは次のようになります。さらに複雑です。ただし、ほとんどのプロジェクトでは、ショートカットを使用して、カテゴリIDの二重の役割をツリーの表示順序として指定することができます。

    コメントを再確認してください:

    はい、「兄弟の並べ替え順序」をクロージャテーブルの別の列として保存し、ancestorの代わりにその値を使用できます。 ブレッドクラムストリングを作成します。しかし、そうすると、多くのデータの冗長性が発生します。つまり、特定の祖先は、その祖先から派生するパスごとに1つずつ、複数の行に格納されます。したがって、これらすべての行で兄弟の並べ替え順序に同じ値を格納する必要があります。これにより、異常のリスクが発生します。

    別の方法は、1つだけで別のテーブルを作成することです。 ツリー内の個別の祖先ごとに行を作成し、そのテーブルに結合して兄弟の順序を取得します。

    CREATE TABLE category_closure_order (
      ancestor INT PRIMARY KEY,
      sibling_order SMALLINT UNSIGNED NOT NULL DEFAULT 1
    );
    
    SELECT c2.*, cc2.ancestor AS `_parent`,
      GROUP_CONCAT(o.sibling_order ORDER BY breadcrumb.depth DESC) AS breadcrumbs
    FROM category AS c1
    JOIN category_closure AS cc1 ON (cc1.ancestor = c1.id)
    JOIN category AS c2 ON (cc1.descendant = c2.id)
    LEFT OUTER JOIN category_closure AS cc2 ON (cc2.descendant = c2.id AND cc2.depth = 1)
    JOIN category_closure AS breadcrumb ON (cc1.descendant = breadcrumb.descendant)
    JOIN category_closure_order AS o ON breadcrumb.ancestor = o.ancestor
    WHERE c1.id = 1/*__ROOT__*/ AND c1.active = 1
    GROUP BY cc1.descendant
    ORDER BY breadcrumbs;
    
    +----+------------+--------+---------+-------------+
    | id | name       | active | _parent | breadcrumbs |
    +----+------------+--------+---------+-------------+
    |  1 | Cat 1      |      1 |    NULL | 1           |
    |  3 | Cat  1.1   |      1 |       1 | 1,1         |
    |  4 | Cat  1.1.1 |      1 |       3 | 1,1,1       |
    |  7 | Cat 1.1.2  |      1 |       3 | 1,1,2       |
    |  6 | Cat 1.2    |      1 |       1 | 1,2         |
    +----+------------+--------+---------+-------------+
    



    1. PHP / MySQLサイトでの同時ユーザーログインを防ぐ方法は?

    2. PHPスクリプトの日時

    3. WHERE句の参照エイリアス(SELECTで計算)

    4. MySQLでのREGEXP_SUBSTR()関数のしくみ