8

MySQL に階層テーブルがあります。各項目のフィールドは、その親項目parentのフィールドを指しています。ここで説明されているクエリidを使用して、アイテムごとに [深さに関係なく] すべての親のリストを取得できます。フルパスを単一の文字列として取得します。GROUP_CONCAT

SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM (
SELECT  @r AS _id,
         (
         SELECT  @r := parent
         FROM    t_hierarchy
         WHERE   id = _id
         ) AS parent,
         @l := @l + 1 AS lvl
 FROM    (
         SELECT  @r := 200,
                 @l := 0
         ) vars,
         t_hierarchy h
WHERE    @r <> 0
ORDER BY lvl DESC
) x

アイテムの[この場合]idが修正された場合にのみ、これを機能させることができます。200

すべての行に対して同じことをしたい:path完全なパスを表示する 1 つの追加フィールド ( ) を使用してテーブル全体を取得します。私の頭に浮かぶ唯一の解決策は、このクエリを別の選択でラップし、一時変数@idを設定して、サブクエリ内で使用することです。しかし、うまくいきません。私NULLはフィールドでsを取得しpathます。

SELECT @id := id, parent, (
    SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM (
    SELECT  @r AS _id,
             (
             SELECT  @r := parent
             FROM    t_hierarchy
             WHERE   id = _id
             ) AS parent,
             @l := @l + 1 AS lvl
     FROM    (
             SELECT  @r := @id,
                     @l := 0
             ) vars,
             t_hierarchy h
    WHERE    @r <> 0
    ORDER BY lvl DESC
    ) x
) as path
 FROM t_hierarchy

PSパスを別のフィールドに保存して、挿入/更新時に更新できることはわかっていますが、リンクリスト手法に基づくソリューションが必要です。

更新:for再帰やandのような構造を使用しないソリューションを見たいと思いwhileます。パスを見つけるための上記の方法は、ループや関数を使用しません。同じ論理で解決策を見つけたい。または、それが不可能な場合は、その理由を説明してください!

4

2 に答える 2

2

getPath 関数を定義し、次のクエリを実行します。

select id, parent, dbo.getPath(id) as path from t_hierarchy 

getPath 関数の定義:

create function dbo.getPath( @id int)
returns varchar(400)
as
begin
declare @path varchar(400)
declare @term int
declare @parent varchar(100)
set @path = ''
set @term = 0
while ( @term <> 1 )
begin
   select @parent = parent from t_hierarchy where id = @id
   if ( @parent is null or @parent = '' or  @parent = @id )
        set @term = 1
   else
        set @path = @path + @parent   
   set @id = @parent     
end
return @path
end
于 2012-08-29T05:10:41.520 に答える
2

次の 2 つのクエリの違いを考えてみましょう。

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', @id)
) as path
FROM t_hierarchy;

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', _id)
    FROM (SELECT @id as _id) as x
) as path
FROM t_hierarchy;

それらはほぼ同じように見えますが、劇的に異なる結果をもたらします。私のバージョンの MySQL では_id、2 番目のクエリは結果セットの各行で同じでidあり、最後の行と同じです。ただし、2 つのクエリを指定された順序で実行したため、最後のビットは真です。SET @id := 1たとえば、afterはステートメント_idの値と常に等しいことがわかります。SET

それで、ここで何が起こっているのですか?AnEXPLAINは手がかりをもたらします。

mysql>     explain SELECT @id := id as id, parent, (
    ->         SELECT concat(id, ': ', _id)
    ->         FROM (SELECT @id as _id) as x
    ->     ) as path
    ->     FROM t_hierarchy;
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
| id | select_type        | table       | type   | possible_keys | key              | key_len | ref  | rows | Extra          |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
|  1 | PRIMARY            | t_hierarchy | index  | NULL          | hierarchy_parent | 9       | NULL | 1398 | Using index    |
|  2 | DEPENDENT SUBQUERY | <derived3>  | system | NULL          | NULL             | NULL    | NULL |    1 |                |
|  3 | DERIVED            | NULL        | NULL   | NULL          | NULL             | NULL    | NULL | NULL | No tables used |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
3 rows in set (0.00 sec)

その 3 行目 (DERIVEDテーブルが使用されていないテーブル) は、いつでも 1 回だけ計算できることを MySQL に示します。サーバーは、派生テーブルがクエリの他の場所で定義された変数を使用していることに気付かず、行ごとに 1 回実行する必要があるという手がかりもありません。ユーザー定義変数に関する MySQL ドキュメントに記載されている動作に悩まされています。

原則として、ユーザー変数に値を割り当てたり、同じステートメント内で値を読み取ったりしないでください。期待どおりの結果が得られる可能性がありますが、これは保証されません。ユーザー変数を含む式の評価順序は定義されておらず、特定のステートメント内に含まれる要素に基づいて変わる場合があります。さらに、この順序は、MySQL サーバーのリリース間で同じであるとは限りません。

私の場合、@idouter によって (再) 定義される前に、最初にそのテーブルを計算することを選択しますSELECT。実際、元の階層データ クエリが機能するのはまさにそのためです。定義は、@rクエリ内の他の何よりも前に MySQL によって計算されます。これは、まさにそのような派生テーブルであるためです。ただし、ここでは@r、クエリ全体に対して 1 回だけでなく、テーブルの行ごとに 1 回リセットする方法が必要です。そのためには、手動でリセットして、元のクエリのように見えるクエリが必要@rです。

SELECT  @r := if(
          @c = th1.id,
          if(
            @r is null,
            null,
            (
              SELECT  parent
              FROM    t_hierarchy
              WHERE   id = @r
            )
          ),
          th1.id
        ) AS parent,
        @l := if(@c = th1.id, @l + 1, 0) AS lvl,
        @c := th1.id as _id
FROM    (
        SELECT  @c := 0,
                @r := 0,
                @l := 0
        ) vars
        left join t_hierarchy as th1 on 1
        left join t_hierarchy as th2 on 1
HAVING  parent is not null

このクエリはt_hierarchy、元のクエリと同じ方法で 2 番目を使用して、親サブクエリがループするのに十分な行が結果にあることを確認します。また、自分自身を親として含む各 _id の行を追加します。そうしないと、(NULL親フィールドにある) ルート オブジェクトが結果にまったく表示されません。

奇妙なことに、結果を実行するGROUP_CONCATと順序が乱れるようです。幸いなことに、その関数には独自のORDER BY句があります。

SELECT  _id,
        GROUP_CONCAT(parent ORDER BY lvl desc SEPARATOR ' > ') as path,
        max(lvl) as depth
FROM    (
  SELECT  @r := if(
            @c = th1.id,
            if(
              @r is null,
              null,
              (
                SELECT  parent
                FROM    t_hierarchy
                WHERE   id = @r
              )
            ),
            th1.id
          ) AS parent,
          @l := if(@c = th1.id, @l + 1, 0) AS lvl,
          @c := th1.id as _id
  FROM    (
          SELECT  @c := 0,
                  @r := 0,
                  @l := 0
          ) vars
          left join t_hierarchy as th1 on 1
          left join t_hierarchy as th2 on 1
  HAVING  parent is not null
  ORDER BY th1.id
) as x
GROUP BY _id;

@r公正な警告: これらのクエリは、@l更新の前に発生した更新に暗黙的に依存しています@c。その順序は MySQL によって保証されておらず、サーバーのバージョンによって変わる可能性があります。

于 2012-08-29T18:36:12.220 に答える