MySQL階層再帰クエリを作成する方法


271

次のようなMySQLテーブルがあります。

id | name        | parent_id
19 | category1   | 0
20 | category2   | 19
21 | category3   | 20
22 | category4   | 21
......

ここで、IDを指定するだけの単一のMySQLクエリを作成します(たとえば、 'id = 19'と言います)、そのすべての子IDを取得する必要があります(つまり、結果にはID '20、21,22 'が必要です)。 ...また、子の階層は不明である場合があります。

また、forループを使用した解決策はすでにあります...可能であれば、単一のMySQLクエリを使用して同じことを実現する方法を教えてください。


階層が7レベルの深さであるとします。出力テーブルはどのようになると思いますか?
ジョナサンレフラー2013年

1
MySQL(まだ)は階層クエリをサポートしていません(他の最新のDBMSはサポートしています)。ストアドプロシージャを作成するか、別のデータモデルを使用する必要があります。
a_horse_with_no_name 2013年


1
MYSQL 8.0は、CTE(Common Table Expressions)を使用した再帰クエリをサポートします
user3712320

最後のコメントIDから始まる投稿の完全なリストを取得するのはどうですか?または最後の子供?
joe

回答:


393

以下のためのMySQL 8+:再帰的な使用with構文を。
以下のためのMySQL 5.xの:使用のインライン変数、パスID、または自己結合します。

MySQL 8以降

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

で指定された値は、すべての子孫を選択する親のにparent_id = 19設定する必要がidあります。

MySQL 5.x

共通テーブル式をサポートしないMySQLバージョン(バージョン5.7まで)では、次のクエリでこれを実現します。

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv)
and     length(@pv := concat(@pv, ',', id))

こちらがフィドルです。

ここで、で指定された値は、すべての子孫を選択する親のに@pv := '19'設定する必要がidあります。

これは、親が複数の子を持つ場合にも機能します。ただし、各レコードは条件を満たす必要parent_id < idがあります。そうでない場合、結果は完全ではありません。

クエリ内の変数の割り当て

このクエリは特定のMySQL構文を使用します。変数はその実行中に割り当てられ、変更されます。実行の順序についていくつかの仮定が行われます。

  • from句が最初に評価されます。これ@pvが初期化される場所です。
  • where句がから検索のための各レコードについて評価されるfromエイリアス。したがって、ここでは、子孫ツリーにあると親が既に識別されているレコードのみが含まれるように条件が設定されます(プライマリ親のすべての子孫がに徐々に追加されます@pv)。
  • このwhere句の条件は順番に評価され、全体の結果が確定すると評価は中断されます。したがって、2番目の条件idはを親リストに追加するため、2番目の条件にする必要があります。これは、idが最初の条件を通過した場合にのみ発生します。length機能のみを確認してください、この条件があっても、常に真であることを確認するために呼び出されpvた文字列が何らかの理由でfalsy値をもたらすであろう。

全体として、これらの仮定は信頼できないほど信頼できない場合があります。ドキュメントは警告しています:

期待どおりの結果が得られるかもしれませんが、これは保証されていません[...]ユーザー変数を含む式の評価順序は未定義です。

したがって、上記のクエリと一貫して機能しますが、たとえば条件を追加したり、このクエリをより大きなクエリのビューまたはサブクエリとして使用したりすると、評価順序が変更される可能性があります。これは、将来のMySQLリリースで削除される「機能」です。

MySQLの以前のリリースでは、以外のステートメントでユーザー変数に値を割り当てることが可能SETでした。この機能は、下位互換性のためにMySQL 8.0でサポートされていますが、MySQLの将来のリリースでは削除される可能性があります。

上記のように、MySQL 8.0以降では、再帰with構文を使用する必要があります。

効率

非常に大きなデータセットの場合find_in_set、リスト内の数値を検索する最も理想的な方法ではなく、返されるレコード数と同じ桁のサイズに達するリスト内ではないため、このソリューションは遅くなる可能性があります。

オルタナティブ1: with recursiveconnect by

より多くのデータベースが実装するSQL:1999 ISO標準WITH [RECURSIVE]構文再帰クエリのために(例えばPostgresの8.4+SQL Serverの2005+DB2Oracleの11gR2の+SQLiteの3.8.4+Firebirdの2.1+H2HyperSQL 2.1.0+TeradataのMariaDB 10.2.2以降)。また、バージョン8.0以降、MySQLでもサポートされています。使用する構文については、この回答の上部を参照してください。

一部のデータベースには、OracleDB2InformixCUBRID、およびその他のデータベースでCONNECT BY使用可能な句など、階層ルックアップ用の代替の非標準構文があります。

MySQLバージョン5.7はそのような機能を提供していません。データベースエンジンがこの構文を提供する場合、または提供する構文に移行できる場合、それは確かに最適なオプションです。そうでない場合は、次の代替案も検討してください。

代替2:パススタイルの識別子

id階層情報を含む値、つまりパスを割り当てると、物事がずっと簡単になります。たとえば、あなたの場合、これは次のようになります:

ID       | NAME
19       | category1   
19/1     | category2  
19/1/1   | category3  
19/1/1/1 | category4  

次に、あなたselectはこのようになります:

select  id,
        name 
from    products
where   id like '19/%'

代替3:繰り返し自己結合

階層ツリーの深さの上限がわかっている場合は、次のsqlような標準クエリを使用できます。

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

このフィドルを見る

whereあなたがの子孫を取得したい親の条件を指定します。必要に応じて、このクエリをより多くのレベルで拡張できます。


26
私はあなたの説明が好きです。それは単に答えを与えるだけでなく、なぜそれが問題を解決するのかを説明し実際にそれから学ぶことができます。編集:また、事前にレベル数を知っておく必要がないことも素晴らしいことです。
Byson

1
@バイソン、私はあなたのフィードバックにとても感謝しています。ありがとう!
トリンコット

2
@Avión、これはどこかに置く必要があるものではなく、すべてのレコードについてこの条件が真であることが要件です。1つ以上のレコードがある場合parent_id > id、このソリューションは使用できません。
トリンコット2017年

2
使用することをお探しの方のためWITH RECURSIVEの方法を、私が見つかりました。次の記事を、このような再帰の深さ、distincts、検出および決算サイクルなど、さまざまなシナリオで本当に便利

2
私は自分のコンピューターの自分のテーブルでMySQL5.7のメインソリューションを試しましたが、@ pv:= concat(@pv、 '、'、id)節と同等の評価がfalseであるため、うまくいきませんでした。length(@pv:= concat(@pv、 '、'、id))> 0に変更することで修正し、常にtrueになるようにしました。
KCウォン

82

MySQLで階層データを管理するブログから

テーブル構造

+-------------+----------------------+--------+
| category_id | name                 | parent |
+-------------+----------------------+--------+
|           1 | ELECTRONICS          |   NULL |
|           2 | TELEVISIONS          |      1 |
|           3 | TUBE                 |      2 |
|           4 | LCD                  |      2 |
|           5 | PLASMA               |      2 |
|           6 | PORTABLE ELECTRONICS |      1 |
|           7 | MP3 PLAYERS          |      6 |
|           8 | FLASH                |      7 |
|           9 | CD PLAYERS           |      6 |
|          10 | 2 WAY RADIOS         |      6 |
+-------------+----------------------+--------+

クエリ:

SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';

出力

+-------------+----------------------+--------------+-------+
| lev1        | lev2                 | lev3         | lev4  |
+-------------+----------------------+--------------+-------+
| ELECTRONICS | TELEVISIONS          | TUBE         | NULL  |
| ELECTRONICS | TELEVISIONS          | LCD          | NULL  |
| ELECTRONICS | TELEVISIONS          | PLASMA       | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS  | FLASH |
| ELECTRONICS | PORTABLE ELECTRONICS | CD PLAYERS   | NULL  |
| ELECTRONICS | PORTABLE ELECTRONICS | 2 WAY RADIOS | NULL  |
+-------------+----------------------+--------------+-------+

ほとんどのユーザーは一度にSQLデータベースの階層データを扱ってきましたが、階層データの管理がリレーショナルデータベースの目的ではないことは間違いありません。リレーショナルデータベースのテーブルは(XMLのように)階層的ではなく、単なるフラットリストです。階層データには親子関係があり、リレーショナルデータベーステーブルでは本来表現されません。 続きを読む

詳細については、ブログを参照してください。

編集:

select @pv:=category_id as category_id, name, parent from category
join
(select @pv:=19)tmp
where parent=@pv

出力:

category_id name    parent
19  category1   0
20  category2   19
21  category3   20
22  category4   21

参照:Mysqlで再帰的なSELECTクエリを行う方法?


23
階層の最大レベルが4つ以下であれば問題ありません。Nレベルがある場合、クエリを正しく作成するには、そのことを知っている必要があります。
ジョナサンレフラー2013年

2
@Damodaran、返信ありがとう...必要なのは、子の数がわからない状態です...そして、内部結合の概念を使用しているブログでは、階層がわからないことが必要です。私の場合...であなたの見解を教えてください...それで、簡単な言葉で私は 'n'が知られていない 'n'ヒレラシーレベルを処理するクエリが必要です.....
Tarun Parswani

1
@ user3036105:これをMySQLで単一の SQLクエリで行うことはできません。MySQLはそのために十分に進歩していません。これが本当に必要な場合は、再帰クエリをサポートするDBMSへのアップグレードを検討してください。
a_horse_with_no_name 2013年

5
>ほとんどのユーザーは、SQLデータベースの階層データを扱っていましたが、階層データの管理がリレーショナルデータベースの目的ではないことは間違いありません。たぶん、あなたはMySQLデータベースを意味したのでしょう。Oracleデータベースは、階層データとクエリを非常にうまく処理します。
Peter Nosko、2014

1
「...階層データの管理はリレーショナルデータベースが意図するものではありません...」これはリレーショナルデータベースの本来の意図ではなかったかもしれませんが、実際の階層データは信じられないほど一般的であり、MySQLは実際のシナリオでデータを実際に使用する方法。
Dave L

9

これらを試してください:

テーブル定義:

DROP TABLE IF EXISTS category;
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20),
    parent_id INT,
    CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
    REFERENCES category (id)
) engine=innodb;

実験的な行:

INSERT INTO category VALUES
(19, 'category1', NULL),
(20, 'category2', 19),
(21, 'category3', 20),
(22, 'category4', 21),
(23, 'categoryA', 19),
(24, 'categoryB', 23),
(25, 'categoryC', 23),
(26, 'categoryD', 24);

再帰的なストアドプロシージャ:

DROP PROCEDURE IF EXISTS getpath;
DELIMITER $$
CREATE PROCEDURE getpath(IN cat_id INT, OUT path TEXT)
BEGIN
    DECLARE catname VARCHAR(20);
    DECLARE temppath TEXT;
    DECLARE tempparent INT;
    SET max_sp_recursion_depth = 255;
    SELECT name, parent_id FROM category WHERE id=cat_id INTO catname, tempparent;
    IF tempparent IS NULL
    THEN
        SET path = catname;
    ELSE
        CALL getpath(tempparent, temppath);
        SET path = CONCAT(temppath, '/', catname);
    END IF;
END$$
DELIMITER ;

ストアドプロシージャのラッパー関数:

DROP FUNCTION IF EXISTS getpath;
DELIMITER $$
CREATE FUNCTION getpath(cat_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getpath(cat_id, res);
    RETURN res;
END$$
DELIMITER ;

例を選択:

SELECT id, name, getpath(id) AS path FROM category;

出力:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 19 | category1 | category1                               |
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
| 23 | categoryA | category1/categoryA                     |
| 24 | categoryB | category1/categoryA/categoryB           |
| 25 | categoryC | category1/categoryA/categoryC           |
| 26 | categoryD | category1/categoryA/categoryB/categoryD |
+----+-----------+-----------------------------------------+

特定のパスを持つ行のフィルタリング:

SELECT id, name, getpath(id) AS path FROM category HAVING path LIKE 'category1/category2%';

出力:

+----+-----------+-----------------------------------------+
| id | name      | path                                    |
+----+-----------+-----------------------------------------+
| 20 | category2 | category1/category2                     |
| 21 | category3 | category1/category2/category3           |
| 22 | category4 | category1/category2/category3/category4 |
+----+-----------+-----------------------------------------+

1
これは複数の子には機能しません。例(20, 'category2', 19), (21, 'category3', 20), (22, 'category4', 20),
クリーンコード

3
私はそれが複数の子供のためにうまくいくと確信しています。私もそれをも​​う一度テストしました。
ファンディ・スサント2018

@Fandi Susanto、ありがとうAloteが私を助けてくれてありがとう。
Dogan Ozer

9

私が思いついた最善のアプローチは

  1. 系統を使用して、ツリーを格納/ソート/トレースします。それは十分以上であり、他のどのアプローチよりも読書のために何千倍も速く動作します。また、DBが変更された場合でも、そのパターンを維持することができます(どのデータベースでもそのパターンを使用できるため)
  2. 特定のIDの系統を決定する関数を使用します。
  3. 必要に応じて使用してください(選択で、CUD操作で、またはジョブで)。

系統アプローチ記述子。どこでも見つけることができます。たとえば、 ここまたはここ。機能に関しては- それが私に影響を与えたものです。

結局のところ、多かれ少なかれシンプルで、比較的高速で、シンプルなソリューションを得ました。

関数の本体

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` FUNCTION `get_lineage`(the_id INT) RETURNS text CHARSET utf8
    READS SQL DATA
BEGIN

 DECLARE v_rec INT DEFAULT 0;

 DECLARE done INT DEFAULT FALSE;
 DECLARE v_res text DEFAULT '';
 DECLARE v_papa int;
 DECLARE v_papa_papa int DEFAULT -1;
 DECLARE csr CURSOR FOR 
  select _id,parent_id -- @n:=@n+1 as rownum,T1.* 
  from 
    (SELECT @r AS _id,
        (SELECT @r := table_parent_id FROM table WHERE table_id = _id) AS parent_id,
        @l := @l + 1 AS lvl
    FROM
        (SELECT @r := the_id, @l := 0,@n:=0) vars,
        table m
    WHERE @r <> 0
    ) T1
    where T1.parent_id is not null
 ORDER BY T1.lvl DESC;
 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    open csr;
    read_loop: LOOP
    fetch csr into v_papa,v_papa_papa;
        SET v_rec = v_rec+1;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- add first
        IF v_rec = 1 THEN
            SET v_res = v_papa_papa;
        END IF;
        SET v_res = CONCAT(v_res,'-',v_papa);
    END LOOP;
    close csr;
    return v_res;
END

そして、あなたはちょうど

select get_lineage(the_id)

それが誰かを助けることを願っています:)


9

ここで別の質問について同じことをしましたか

Mysqlは再帰的に複数のレベルを持つすべての子を選択します

クエリは次のようになります。

SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
  SELECT @pv:=(
    SELECT GROUP_CONCAT(id SEPARATOR ',')
    FROM table WHERE parent_id IN (@pv)
  ) AS lv FROM table 
  JOIN
  (SELECT @pv:=1)tmp
  WHERE parent_id IN (@pv)
) a;

どうすればよいですか? SELECT idFolder, (SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM ( SELECT @pv:=(SELECT GROUP_CONCAT(idFolder SEPARATOR ',') FROM Folder WHERE idFolderParent IN (@pv)) AS lv FROM Folder JOIN (SELECT @pv:= F1.idFolder )tmp WHERE idFolderParent IN (@pv)) a) from folder F1 where id > 10; @pvのF1.idFolderを参照できません
Rahul

コメントに示されているデータを使用して、OPの元の質問からテーブルを再作成し、ここでクエリを実行しNULLて、結果として単一のクエリを取得しました。なぜそうなのか知っていますか?データベースエンジンに関して前提条件はありますか、またはこのクエリを実行してからこのクエリが古くなっているために何か変更がありましたか?
デジタル忍者

7

迅速な読み取り速度が必要な場合、最適なオプションはクロージャーテーブルを使用することです。クロージャーテーブルには、祖先/子孫の各ペアの行が含まれます。したがって、例では、クロージャテーブルは次のようになります。

ancestor | descendant | depth
0        | 0          | 0
0        | 19         | 1
0        | 20         | 2
0        | 21         | 3
0        | 22         | 4
19       | 19         | 0
19       | 20         | 1
19       | 21         | 3
19       | 22         | 4
20       | 20         | 0
20       | 21         | 1
20       | 22         | 2
21       | 21         | 0
21       | 22         | 1
22       | 22         | 0

このテーブルを作成すると、階層クエリが非常に簡単かつ高速になります。カテゴリ20のすべての子孫を取得するには:

SELECT cat.* FROM categories_closure AS cl
INNER JOIN categories AS cat ON cat.id = cl.descendant
WHERE cl.ancestor = 20 AND cl.depth > 0

もちろん、このような非正規化されたデータを使用する場合は常に大きなマイナス面があります。あなたはあなたのカテゴリーテーブルと一緒にクロージャーテーブルを維持する必要があります。最善の方法はおそらくトリガーを使用することですが、クロージャーテーブルの挿入/更新/削除を正しく追跡するのはやや複雑です。何でもそうであるように、あなたはあなたの要件を見て、どのアプローチがあなたにとって最善であるかを決める必要があります。

編集リレーショナルデータベースに階層データを保存するためのオプションは何ですか?の質問を参照してくださいより多くのオプションについて。状況に応じて最適なソリューションは異なります。


4

最初の再帰の子をリストする単純なクエリ:

select @pv:=id as id, name, parent_id
from products
join (select @pv:=19)tmp
where parent_id=@pv

結果:

id  name        parent_id
20  category2   19
21  category3   20
22  category4   21
26  category24  22

...左結合で:

select
    @pv:=p1.id as id
  , p2.name as parent_name
  , p1.name name
  , p1.parent_id
from products p1
join (select @pv:=19)tmp
left join products p2 on p2.id=p1.parent_id -- optional join to get parent name
where p1.parent_id=@pv

すべての子をリストする@tincotの解決策:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv) > 0
and     @pv := concat(@pv, ',', id)

Sql Fiddleを使用してオンラインでテストし、すべての結果を確認してください。

http://sqlfiddle.com/#!9/a318e3/4/0


3

再帰クエリ(パフォーマンスのYMMV)を使用すると、他のデータベースでもこのように簡単に行うことができます。

これを行うもう1つの方法は、左と右の値の2つの追加ビットデータを格納することです。左と右の値は、表現しているツリー構造の事前トラバーサルから派生します。

これはModified Preorder Tree Traversalと呼ばれ、簡単なクエリを実行してすべての親値を一度に取得できます。「ネストされたセット」という名前も付いています。


同様のコメントをあなたのコメントに追加したかったのですが、あなたが追加したので、「ネストされたセット」の良い例へのリンクだけを追加します:mikehillyer.com/articles/managing-hierarchical-data-in-mysql
Miroslawオポカ

2

mysqlの自己関係テーブルのツリーを作成するには、BlueM / tree phpクラスを使用するだけです。

TreeおよびTree \ Nodeは、親ID参照を使用して階層的に構造化されたデータを処理するためのPHPクラスです。典型的な例は、各レコードの「親」フィールドが別のレコードの主キーを参照するリレーショナルデータベースのテーブルです。もちろん、Treeはデータベースからのデータだけでなく、何でも使用できます。データをどこから取得し、どのように処理したかに関係なく、ユーザーがデータを提供し、Treeがそれを使用します。続きを読む

BlueM / treeの使用例を次に示します。

<?php 
require '/path/to/vendor/autoload.php'; $db = new PDO(...); // Set up your database connection 
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title'); 
$records = $stm->fetchAll(PDO::FETCH_ASSOC); 
$tree = new BlueM\Tree($records); 
...

2

ここに画像の説明を入力してください

これはカテゴリテーブルです。

SELECT  id,
        NAME,
        parent_category 
FROM    (SELECT * FROM category
         ORDER BY parent_category, id) products_sorted,
        (SELECT @pv := '2') initialisation
WHERE   FIND_IN_SET(parent_category, @pv) > 0
AND     @pv := CONCAT(@pv, ',', id)

出力:: ここに画像の説明を入力してください


1
これを説明できますか?しかし、私はこれが機能していることを保証します。ありがとうございました。
wobsoriano

1
plzはクエリを説明し、@ pvの意味は何ですか?このクエリでループはどのように機能していますか?
アマンジョットカウル

2
親よりもIDが低い子がある場合、すべてのレベルで機能するとは思われません。:(
ジョナス

1
@Jonasは、実際の問題を特定するのに20分かかり、さまざまな組み合わせを試してみました。はい、あなたが正しい。親IDよりも小さいIDでは機能しません。何か解決策はありますか?
muaaz

@muaaz私は最終的に、それぞれの行のパスを含む「パス」フィールドを使用してそれを解決しました。たとえば、ID 577の行にはパス「/ 1/2/45/577 /」があります。ID 2のすべての子を探している場合は、パスLIKE "/ 1/2 /%"のすべての行を選択できます。唯一の欠点は、更新メソッドでパスを更新する必要があることです。しかし、MySQL 5.6(互換)の場合、それが私にとって有効な唯一のソリューションでした。
Jonas

1

それは少しトリッキーなものです、あなたのために働いているかどうかこれをチェックしてください

select a.id,if(a.parent = 0,@varw:=concat(a.id,','),@varw:=concat(a.id,',',@varw)) as list from (select * from recursivejoin order by if(parent=0,id,parent) asc) a left join recursivejoin b on (a.id = b.parent),(select @varw:='') as c  having list like '%19,%';

SQLフィドルリンクhttp://www.sqlfiddle.com/#!2/e3cdf/2

フィールドとテーブル名に適切に置き換えます。


この場合、sqlfiddle.com/#!2/19360/2は機能しません。このトリックでは、少なくとも最初に階層レベルで順序付けする必要があります。
Jaugar Chang 2014

1

ここで言及されていないものは、受け入れられた回答の2番目の選択肢に少し似ていますが、大きな階層クエリと簡単な(更新と削除の挿入)アイテムのコストが異なり、低コストですが、各アイテムに永続パス列を追加します。

いくつかのような:

id | name        | path
19 | category1   | /19
20 | category2   | /19/20
21 | category3   | /19/20/21
22 | category4   | /19/20/21/22

例:

-- get children of category3:
SELECT * FROM my_table WHERE path LIKE '/19/20/21%'
-- Reparent an item:
UPDATE my_table SET path = REPLACE(path, '/19/20', '/15/16') WHERE path LIKE '/19/20/%'

パスの長さを最適化し、ORDER BY path実際の数値パスIDの代わりにbase36エンコーディングを使用する

 // base10 => base36
 '1' => '1',
 '10' => 'A',
 '100' => '2S',
 '1000' => 'RS',
 '10000' => '7PS',
 '100000' => '255S',
 '1000000' => 'LFLS',
 '1000000000' => 'GJDGXS',
 '1000000000000' => 'CRE66I9S'

https://en.wikipedia.org/wiki/Base36

エンコードされたIDへの固定長とパディングを使用して、スラッシュ「/」区切り文字も抑制します。

詳細な最適化の説明はこちら:https : //bojanz.wordpress.com/2014/04/25/storing-hierarchical-data-materialized-path/

TODO

1つのアイテムの祖先を取得するためのパスを分割する関数またはプロシージャを構築する


ありがとう!興味深いbase36
Vlad

0

これは私にとってはうまくいきます。これがあなたにとってもうまくいくことを願っています。特定のメニューのルートを子に設定するレコードを提供します。必要に応じてフィールド名を変更します。

SET @id:= '22';

SELECT Menu_Name, (@id:=Sub_Menu_ID ) as Sub_Menu_ID, Menu_ID 
FROM 
    ( SELECT Menu_ID, Menu_Name, Sub_Menu_ID 
      FROM menu 
      ORDER BY Sub_Menu_ID DESC
    ) AS aux_table 
    WHERE Menu_ID = @id
     ORDER BY Sub_Menu_ID;

親よりもIDが大きい子がいる場合、すべてのレベルで機能するとは限りません
muaaz

-1

私はそれをもっと簡単に見つけました:

1)アイテムが別のアイテムの親階層のどこかにあるかどうかをチェックする関数を作成します。このようなもの(私は関数を書きません、WHILE DOでそれを作ります):

is_related(id, parent_id);

あなたの例では

is_related(21, 19) == 1;
is_related(20, 19) == 1;
is_related(21, 18) == 0;

2)次のような副選択を使用します。

select ...
from table t
join table pt on pt.id in (select i.id from table i where is_related(t.id,i.id));

-1

お問い合わせをさせていただきました。これにより、1つのクエリで再帰的なカテゴリが得られます。

SELECT id,NAME,'' AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 WHERE prent is NULL
UNION 
SELECT b.id,a.name,b.name AS subName,'' AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id WHERE a.prent is NULL AND b.name IS NOT NULL 
UNION 
SELECT c.id,a.name,b.name AS subName,c.name AS subsubName,'' AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id WHERE a.prent is NULL AND c.name IS NOT NULL 
UNION 
SELECT d.id,a.name,b.name AS subName,c.name AS subsubName,d.name AS subsubsubName FROM Table1 AS a LEFT JOIN Table1 AS b ON b.prent=a.id LEFT JOIN Table1 AS c ON c.prent=b.id LEFT JOIN Table1 AS d ON d.prent=c.id WHERE a.prent is NULL AND d.name IS NOT NULL 
ORDER BY NAME,subName,subsubName,subsubsubName

こちらがフィドルです。


あなたの肯定的な評判を取り戻すためにあなたの答えを削除/編集してください。
fWd82
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.