22

このようにテーブルをソートする最良の方法は何ですか:

CREATE TABLE category(
    id INT(10),
    parent_id INT(10),
    name VARCHAR(50)
);

INSERT INTO category (id, parent_id, name) VALUES
(1, 0, 'pizza'),        --node 1
(2, 0, 'burger'),       --node 2
(3, 0, 'coffee'),       --node 3
(4, 1, 'piperoni'),     --node 1.1
(5, 1, 'cheese'),       --node 1.2
(6, 1, 'vegetariana'),  --node 1.3
(7, 5, 'extra cheese'); --node 1.2.1

IDまたは名前で階層的にソートするには:
'pizza' //node 1
'piperoni' //node 1.1
'cheese' //node 1.2
'extra Cheese' //node 1.2.1
'vegetariana' //node 1.3
'burger' //ノード 2
'コーヒー' //ノード 3

編集:名前の末尾の数字は、構造をよりよく視覚化するためのものであり、並べ替え用ではありません。

編集 2:name何度か述べたように... 「cheese 1.2 」の末尾の数字は、並べ替えではなく、視覚化のみを目的としています。コメントとして移動しましたが、あまりにも多くの人が混乱してしまいました。

4

8 に答える 8

14

パス列とトリガーを追加することで、これはかなり簡単に実行できます。

最初に、ルートからノードへのパスを含む varchar 列を追加します。

ALTER TABLE category ADD path VARCHAR(50) NULL;

次に、挿入時にパスを計算するトリガーを追加します。

(単に新しい ID を親のパスと連結するだけです)

CREATE TRIGGER set_path BEFORE INSERT ON category
  FOR EACH ROW SET NEW.path = 
  CONCAT(IFNULL((select path from category where id = NEW.parent_id), '0'), '.', New.id);

次に、パスによる順序を選択するだけです。

SELECT name, path FROM category ORDER BY path;

結果:

pizza         0.1
piperoni      0.1.4
cheese        0.1.5
extra cheese  0.1.5.7
vegetariana   0.1.6
burger        0.2
coffee        0.3

フィドルを参照してください。

この方法では、メンテナンス コストも最小限に抑えることができます。パス フィールドは挿入時に非表示になり、トリガーを介して計算されます。ノードのすべての子も削除されるため、ノードを削除してもオーバーヘッドはありません。唯一の問題は、ノードのparent_idを更新するときです。まあ、それをしないでください !:)

于 2013-02-26T03:43:53.893 に答える
13

ネストされたツリー セットを列と組み合わせて使用​​することlevelは、ツリー ベースの構造を読み取って並べ替えるための非常に優れた手法です。サブツリーを選択し、結果を特定のレベルに制限し、1 つのクエリで並べ替えを行うのは簡単です。ただし、全体の挿入と削除のコストは比較的高いため、データを書き込むよりも頻繁にクエリを実行する場合や、読み取りパフォーマンスが重要な場合に使用する必要があります。(50 ~ 100 の場合、要素の削除、挿入、または移動の時間は問題にならないはずです。1000 であっても、問題にはならないはずです)。

保存する各エントリには、 と の値levelが含まれています。以下のサンプルでは、​​ ( 、、 )の子孫のみを選択する場合は、次のようにします。leftrightleftrightlevel1.2

 SELECT * FROM table WHERE left >=7 AND right <=16

子供だけを選択したい場合

 SELECT * FROM table WHERE left >=7 AND right <=16 AND level=2

ソートしたい場合は、行うことができます

 SELECT * FROM table WHERE left >=7 AND right <=16 ORDER BY left

階層のグループ化を維持しながら他のフィールドで並べ替えると、並べ替え方法によっては問題が発生する可能性があります。

                               1 (0,17,0)
                                   |
                                   |
                   +---------------+---------------------------------------+
                   |                                                       |
              1.1 (1,6,1)                                            1.2 (7,16,1)
                   |                                                       |
      +------------+-------+                  +-------------------+--------+----------------+
      |                    |                  |                   |                         |
  1.1.1 (2,3,2)      1.1.2 (4,5,2)      1.2.1 (8,9,2)       1.2.2 (10,13,2)         1.2.2 (14,15,2)
                                                                  |
                                                                  |
                                                                  |
                                                            1.2.2.1 (11,12,3)

Closure Table (完成用ですが、ユースケースにはお勧めしません)。すべてのパスをツリーに保存するため、多くのレベルがある場合、階層に必要なストレージ スペースは非常に急速に増大します。

Path Enumerationでは、各要素のパスを entry で保存します。パスの/0/クエリ/0/1/は簡単ですが、ソートの場合はそれほど柔軟ではありません。

少量の全体については、Nested Tree Setsを使用します。残念ながら、これらの手法について説明し、それらを比較する適切なリファレンス ページはありません。

于 2013-02-19T14:27:50.787 に答える
10

ネストが 3 レベルしかない場合は、そのようなことができます

SELECT c1.name FROM category as c1 LEFT JOIN category as c2
   ON c1.parent_id = c2.id OR (c1.parent_id = 0 AND c1.id = c2.id) 
   ORDER BY c2.parent_id, c2.id, c1.id; 

ネストレベルが多い場合は、よりトリッキーになります

より多くのネストレベルについては、関数を書くことができます

delimiter ~
DROP FUNCTION getPriority~

CREATE FUNCTION getPriority (inID INT) RETURNS VARCHAR(255) DETERMINISTIC
begin
  DECLARE gParentID INT DEFAULT 0;
  DECLARE gPriority VARCHAR(255) DEFAULT '';
  SET gPriority = inID;
  SELECT parent_id INTO gParentID FROM category WHERE ID = inID;
  WHILE gParentID > 0 DO
    SET gPriority = CONCAT(gParentID, '.', gPriority);
    SELECT parent_id INTO gParentID FROM category WHERE ID = gParentID;
  END WHILE;
  RETURN gPriority;
end~

delimiter ;

だから私は今

SELECT * FROM category ORDER BY getPriority(ID);

私は持っている

+------+-----------+--------------------+
| ID   | parent_id | name               |
+------+-----------+--------------------+
|    1 |         0 | pizza 1            |
|    4 |         1 | piperoni 1.1       |
|    5 |         1 | cheese 1.2         |
|    7 |         5 | extra cheese 1.2.1 |
|    6 |         1 | vegetariana 1.3    |
|    2 |         0 | burger 2           |
|    3 |         0 | coffee 3           |
+------+-----------+--------------------+
于 2013-02-15T08:13:32.397 に答える
3

誰もがソリューションを設計しすぎていると思います。仮想最上位 ID が 0 の 3 レベルのように、目標が実際に例で表されている場合は、これで十分です。

SELECT *
     , id AS SORT_KEY
  FROM category a
 WHERE parent_id = 0
UNION ALL
SELECT a.*
     , CONCAT(b.id, '.', a.id) AS SORT_KEY
  FROM category a
     , category b
 WHERE b.parent_id = 0
   and b.id = a.parent_id
UNION ALL
SELECT a.*
     , CONCAT(c.id,'.', b.id,'.', a.id) AS SORT_KEY
  FROM category a
     , category b
     , category c
 WHERE c.parent_id = 0
   and b.id = a.parent_id
   AND c.id = b.parent_id
ORDER BY sort_key
于 2013-02-24T18:03:24.730 に答える
3

1 つの方法は、任意のノードのフル パスを格納するための別の文字列フィールドを用意することです。すべての挿入/更新/削除操作でこのフィールドを維持する必要があります。

以下のようなフィールド値を持つことができます

CREATE TABLE category(
    id INT(10),
    parent_id INT(10),
    name VARCHAR(50),
    path VARCHAR(255)
);

INSERT INTO category (id, parent_id, name, path) VALUES
(1, 0, 'pizza 1','|1|'),
(2, 0, 'burger 2','|2|'),
(3, 0, 'coffee 3','|3|'),
(4, 1, 'piperoni 1.1','|1||4|'),
(5, 1, 'cheese 1.2','|1||5|'),
(6, 1, 'vegetariana 1.3','|1||6|'),
(7, 5, 'extra cheese 1.2.1','|1||5||1|');

ツリーを適切な並べ替え順序にするには、パス フィールドで並べ替える必要があります。

SELECT * FROM `category` ORDER BY `path`;

SqlFiddle のデモを見る

この方法では、ツリー全体を正しいソート順で出力するためにプログラミング言語で再帰を行う必要はありません。

Note:

この例は、最大 ID が |1||11| のように 9 までの場合にのみ機能します。|1||2|より早く来る

この問題を解決するには、アプリケーションで予想される ID フィールドの最大値に基づいて文字列を構築するためのパディングを行う必要があります。次の例のように、予想される最大値は 999 (3 桁) です。

|001||002|


私の経験によると、このソリューションは、最大 7 ~ 8 レベルの深さのツリーを処理する場合にのみ適しています。

その他の方法 :ここをクリック

于 2013-02-15T08:50:32.493 に答える
2

SQL

WITH CTE_Category
    AS
    (
      SELECT id, parent_id, name
      , RIGHT(name,CHARINDEX(' ',REVERSE(RTRIM(name)))-1) as ordername
      FROM Category 
    )

    SELECT id, parent_id, name FROM CTE_Category ORDER BY ordername

MySql

SELECT id, parent_id, name
FROM Category ORDER BY SUBSTRING_INDEX(name,' ',-1)
于 2013-02-21T16:32:49.307 に答える
-1
SELECT * FROM category ORDER BY name, parent_id ASC
于 2013-02-15T07:52:20.900 に答える
-1

ORDER BY name , idSQL クエリの最後で試してください。

これは名前でソートし、id を使用して同点を解決します。

于 2013-02-15T07:49:23.407 に答える