次の 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 サーバーのリリース間で同じであるとは限りません。
私の場合、@id
outer によって (再) 定義される前に、最初にそのテーブルを計算することを選択します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 によって保証されておらず、サーバーのバージョンによって変わる可能性があります。