2012-08-20 8 views
8

私はMySQLの階層テーブルを持っています:parent各アイテムのフィールドは親アイテムのidフィールドを指しています。各項目については、query described hereを使用して、すべての親のリストを取得することができます。私はアイテムのidは[それは、この場合に200だ]固定されている場合にのみ、この作業を行うことができます階層テーブル - アイテムのパスを取得する方法[MySQLのリンクされたリスト]

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 

GROUP_CONCATで、私は単一の文字列として完全なパスを取得します。

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

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 

P.S.私は別のフィールドにパスを保存し、挿入/更新するときにそれらを更新することができますが、私はリンクリストのテクニックに基づいて解決策が必要です。

UPDATE:私はforwhileのような再帰や構文を使用しません解決策を見たいのですが。上記のパスの検索方法では、ループや関数は使用されません。私は同じ論理で解決策を見つけたい。それが不可能なら、理由を説明してください!

答えて

1

ある、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 
+0

エラーが表示される: '#1064 - SQL構文に誤りがあります。 '@id int'の近くで使用する正しい構文については、MySQLサーバのバージョンに対応するマニュアルを参照してください。)varchar(400)をbeginとして宣言してください@path varchar(400)@termを '1行目'で宣言し、 ?! –

+0

答えはSQLServer構文です。私はそれをMySQLの構文に変換するのを助けることができます。階層的なデータを持っているので、列を追加したくない、つまり深さ/パスを更新/挿入/削除で更新する必要があります。私はそれがあるのがわかりません。ループなし。 MySQLがCTEを持っていれば、ユーザ定義の関数なしでループなしで同じ問題を解決することができます。 –

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の私のバージョンでは、2番目のクエリの_idは結果セットの各行で同じで、最後の行のidと同じです。しかし、最後のビットは、私が指定された順序で2つのクエリを実行したためにのみ真です。たとえば、SET @id := 1の後には、_idは、SETステートメントの値と常に等しいことがわかります。

ここで何が起こっているのですか? EXPLAINが手掛かりが得られる:第三の行は、使用されないテーブルとDERIVEDテーブルが、それはいつでも、正確に一度だけ計算することができることをMySQLへ示すこと

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) 

。サーバーは、派生テーブルがクエリ内の別の場所で定義された変数を使用していることに気付かず、行ごとに1回実行するというヒントを持っていません。あなたはuser-defined variablesにMySQLのドキュメントに記載された行動にかまされている:

私の場合は

As a general rule, you should never assign a value to a user variable and read the value within the same statement. You might get the results you expect, but this is not guaranteed. The order of evaluation for expressions involving user variables is undefined and may change based on the elements contained within a given statement; in addition, this order is not guaranteed to be the same between releases of the MySQL Server.

@idが(再)外SELECTによって定義される前に、それは、最初にそのテーブルを計算行うことを選択しました。実際、元の階層データクエリが機能するのはそのことです。 @rの定義は、クエリー内の他のものよりも前にMySQLによって計算されます。その種の派生テーブルだからです。しかし、ここではクエリ全体に対して一度だけでなく、テーブル行ごとに一度@rをリセットする必要があります。これを行うには、元のようなクエリが必要で、手で@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 

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

奇妙なことに、GROUP_CONCATで結果を実行すると、順序が乱れるようです。

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; 

フェア警告:これらのクエリは、暗黙のうちに@c更新する前に起こっ@r@lアップデートに頼る幸いなことに、その関数は、独自のORDER BY句を持っています。この注文はMySQLによって保証されておらず、どのバージョンのサーバーでも変更される可能性があります。

+0

この質問にお返事いただきありがとうございます!この答えは、私のために多くの事柄を明らかにする。最終的なクエリは空の結果を返しますが(MySQL 5.1.40)、重要なアイデアがたくさんあるので、私はそれに恩恵を与えています。これを何度か読んで、最後のクエリがDBでうまくいかない理由を理解しようとしますし、いくつかのことを明確にするよう尋ねることもあります。再度、感謝します! –

+0

5.1.40ではうまくいきません。 Ubuntu 11.10 Oneiric上で5.1.63に対してテストされました。あなたは@c行を動かすことができます。 'HAVING'行を削除したり、コメントアウトするためのデバッグを助けるかもしれません。 – eswald