VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
optimizer_switch の設定に由来するため、読者の数値は異なります。そのまま当てはまるのは
各プランの形です。
ルール: マージ可能かどうか
CTE は外側のクエリに折りたたまれるか、先に一時テーブルとして構築されるかの どちらかであり、どちらになるかは、CTE を何回参照したかではなく、CTE の中身に よって決まります。 本体がWHERE と JOIN を伴う単純な SELECT である CTE はマージされ、
プランから完全に消えます。
GROUP BY、
DISTINCT、LIMIT、UNION、またはウィンドウ関数を含む CTE はマージできません。
外側のクエリの条件をそれらの操作の前に適用すると、等価ではなくなるためです。
そのような CTE は実体化されます。
以下のプランは、その 2 番目のケースを示しています。WITH という語ではなく、
CTE の中身に関するものとして読んでください。
どちらになったかを確認する
EXPLAIN FORMAT=TREE は、そのままの言葉でそれを示します。デフォルトの EXPLAIN は
示しません。ここにあるクエリでは、その Extra 列にその語は現れません。
Materialize CTE counts です。その中の
GROUP BY がこれを強制しており、サーバーは行を一時テーブルに書き込んでから
読み戻します。
同じ GROUP BY を含む派生テーブルは、同じプランになります。
FROM (SELECT rating, COUNT(*) ... GROUP BY rating) AS counts と書くと、ツリーは
ノードの名前を除いて同一であり、コストも同じです。したがって、参照が 1 つの
クエリでは、CTE が派生テーブルに優る点は何もありません。2 つの形は読み方が
異なるだけで、オプティマイザは両者を同じように扱います。
同じ CTE を 2 回参照すると、表現が if needed に変わり、2 番目の参照は同じ
実体化された結果を再利用します。
実体化のコスト
実体化された CTE は一時テーブルであり、これは知っておくべき 2 つのことを意味します。 ベーステーブルのインデックスを手放します。行が一時テーブルに置かれると、外側の クエリはそのテーブルをスキャンします。サーバーは、上の結合で行ったように、その場でauto_key を構築することがありますが、それは実際のテーブルにあったインデックスでは
ありません。外側の条件が PRIMARY に届いた、このページ冒頭のマージされたプランと
比較してください。
プッシュダウンがこれを和らげます。derived_condition_pushdown はデフォルトで
有効であり、可能なものを内側に移動します。上の Filter: (count(0) > 200) が
Materialize ノードの外側ではなく内側に現れるのはそのためです。すべてを移動
できるわけではないため、プランが、結果が保持する数よりはるかに多くの行を CTE が
生成していることを示す場合は、制限を自分で CTE の中に書いてください。
CTE が誤った形である場合
- 1 つの短いステップ。 派生テーブルは、それが使われる場所に置いておけます。 1 行のサブクエリに名前を付けると、読者にとって一段の遠回りが増えます。
- オプティマイザがプッシュダウンしなかったフィルタ。 プランが、CTE がすべての 行を実体化し、外側のクエリがそのほとんどを捨てていることを示す場合は、フィルタを 内側に書きます。
- インデックスに対する行ごとの処理。 相関サブクエリまたは結合は、検索のたびに インデックスを使えます。実体化された CTE はすでにそれを手放しています。
FROM 句の途中から派生テーブルを取り出して手で実行するのは、毎回手作業で行う
同じ作業です。
実際の階層に対する再帰 CTE
Sakila には階層がないため、このセクションでは独自のテーブルを使います。サンプル データの横に作成し、使い終わったらDROP TABLE employees; を実行してください。
depth 列は答えであると同時にガードでもあります。
WHERE oc.depth < 100 がそれを防ぎます。無限の探索を有限の探索に
変え、データがきれいかどうかに関係なく機能します。
エラーメッセージは cte_max_recursion_depth を上げるよう提案しますが、それが
正しいのは、再帰が本当に終了し、デフォルトの 1,000 回より多くの回数が必要な場合
だけです。正当な 1,500 ステップの系列は同じエラーに当たり、その場合は
SET SESSION cte_max_recursion_depth = 5000 が正しい答えです。循環のあるデータでは
それは誤った答えです。クエリはやはり終わらず、代わりに後で失敗します。
利用可能性と用語
CTE は MySQL 8.0 で登場し、8.4 と 9.x を含むそれ以降のすべてのリリースに存在します。WITH 句に ERROR 1064 を返すサーバーは、それ以前のものです。SELECT VERSION() で
確認してください。
標準では、再帰 CTE の最初の部分をアンカーメンバー、2 番目の部分を再帰
メンバーと呼びます。チュートリアルは最初の部分をシードと呼びますが、これは同じ
ものであり、MySQL 自身のメッセージは標準の用語を使います。
CTE は INSERT、UPDATE、および DELETE で機能する
トラブルシューティング
関連項目
- MySQL におけるサブクエリと JOIN の比較 — これと競合するもう 1 つの形
- MySQL の GROUP BY のパフォーマンス — CTE が回避を助けてくれる一時テーブル
- MySQL のビュー — 名前付きクエリをスキーマオブジェクトとして永続化する代替手段
- 共通テーブル式 — 先に構文を学びたい場合のチュートリアル