Day8 後半のゴール
「“CTEを使うかどうか”を、自分で選べるようになる」
前半で「CTE=途中結果に名前を付ける仕組み」という感覚はつかめました。
後半では、もう一歩踏み込んで「どんなときにCTEを使うと嬉しいか」「どう書くと読みやすくなるか」「MySQLやSQLiteとの違いをどう意識するか」を整理していきます。
ここでのゴールは、
「これはCTEに切り出した方がいいな」と自分で判断できること。
CTEに“いい名前”を付けて、SQL全体の意図を説明できること。
PostgreSQLでのCTEの振る舞いをざっくり理解しつつ、「まずは可読性優先で使う」というスタンスを持てること、です。
どんなときにCTEを使うと嬉しいか
「“1文でやりたいことが3つ以上ある”ときはCTE候補」
CTEを使うべき典型パターンを、感覚でつかんでおきましょう。
キーワードは「1文の中に“段階”が見えるとき」です。
例えば、こんなSQLを書こうとしているとします。
直近30日間の注文だけを対象にする。
その中から、1万円以上買っているユーザーだけを抽出する。
そのユーザーの中で、メールアドレスが特定ドメインの人だけを出す。
これをCTEで書くと、こうなります。
WITH recent_orders AS (
SELECT *
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days'
),
rich_users AS (
SELECT
user_id,
SUM(amount) AS total_amount
FROM recent_orders
GROUP BY user_id
HAVING SUM(amount) >= 10000
),
target_users AS (
SELECT u.*
FROM users u
JOIN rich_users r ON u.id = r.user_id
WHERE u.email LIKE '%@example.com'
)
SELECT *
FROM target_users;
SQLここでのポイントは、「やりたいことの“段階”とCTEの名前が対応している」ことです。
recent_orders → 直近30日の注文
rich_users → たくさん買っているユーザー
target_users → その中から特定ドメインのユーザー
もしこれをCTEなしで書くと、JOINの中にサブクエリが入り、さらにその中に条件が入り…と、
「どこで何をしているか」が一気に見えにくくなります。
目安として、「1つのSQLで3段階以上の処理をしている」と感じたら、
CTEで分割できないかを考える、という癖をつけるといいです。
CTEに“いい名前”を付けるコツ
「“何をしているか”が一言で分かる名前にする」
CTEは、名前の付け方で読みやすさが大きく変わります。
変数名や関数名と同じで、「中身を見なくても役割が分かる名前」が理想です。
悪い例として、こういう名前を考えてみます。
WITH t1 AS (...),
t2 AS (...),
t3 AS (...)
SELECT * FROM t3;
SQLこれだと、「t1って何?」「t2って何?」と、毎回中身を見ないと意味が分かりません。
さっきの例のように、
recent_orders
rich_users
target_users
といった名前にしておくと、SQLを上から読んだときに「ストーリー」が頭に浮かびます。
CTE名を付けるときのコツは、「日本語で説明するときの名詞を、そのまま英語にする」ことです。
「直近30日の注文」→ recent_orders
「売上の多いユーザー」→ rich_users
「対象ユーザー」→ target_users
SQLは“仕様書の翻訳”なので、「仕様書に出てきそうな言葉」をそのまま名前にするのが一番強いです。
MySQL・SQLiteとの違いを軽く押さえておく
「PostgreSQLはCTEが標準装備、“読めるSQL”を書きやすい」
MySQLやSQLiteでも最近はWITH句が使えますが、
歴史的には「PostgreSQLの方が先にCTEをちゃんと実装していた」世界です。
SQLite:バージョンによってはCTEが使えない/機能が限定的なことがある。
MySQL:バージョン8以降でWITH句が使えるようになったが、古い環境だと使えないこともある。
PostgreSQLを前提にしているなら、「CTEは遠慮なく使っていい標準機能」と考えてOKです。
ここで大事なのは、「MySQL時代に“サブクエリ地獄”で頑張っていたSQL」を、
PostgreSQLに来たタイミングで「CTEで読みやすく書き直す」という発想です。
移植やリファクタリングのときに、
サブクエリをCTEに引き上げる。
ネストを浅くして、上から順に読めるようにする。
という作業をすると、「同じ処理なのに、SQLの理解コストが一気に下がる」体験ができます。
CTEとパフォーマンスの話を“軽く”触れておく
「まずは“可読性優先”、そのあと必要ならチューニング」
CTEには、パフォーマンスの話もあります。
PostgreSQLのバージョンによっては、「CTEは一度必ずマテリアライズ(途中結果を一時的に確定させる)」という挙動をすることがあり、
それが速度に影響するケースもあります。
ただ、Day8の段階では、ここを深追いしなくて大丈夫です。
まずは「CTEでSQLを読みやすく書けるようになる」ことが優先です。
現実的な順番としては、こう考えるのが健全です。
最初は、CTEを使って“意味が分かるSQL”を書く。
そのSQLがボトルネックになったら、EXPLAINで実行計画を見て、必要ならCTEをサブクエリに戻したり、インデックスを調整したりする。
つまり、「最初からパフォーマンスを恐れてCTEを封印する」のではなく、
「まずは読みやすく書いて、必要になったらチューニングする」という順番で考えるのがいいです。
PostgreSQLはプランナが賢いので、「CTEを使ったから即遅い」ということはほとんどありません。
“読みやすさ”というメリットを、まずはしっかり取りに行きましょう。
CTEを使った“リファクタリング練習”のイメージ
「今あるSQLを、WITH句で分割し直してみる」
最後に、実際の練習方法のイメージを共有しておきます。
もしあなたが既にMySQLやSQLiteで、こんなSQLを書いたことがあるなら、
FROMの中にSELECTがネストしている。
さらにその中にGROUP BYやHAVINGが入っている。
WHEREやJOIN条件が長くて、どこで何をしているか分かりにくい。
それをPostgreSQL環境に持ってきて、「CTEで分割し直す」というのが、最高の練習になります。
やることはシンプルです。
サブクエリを1つ抜き出して、WITH句にする。
そのサブクエリに、意味のある名前を付ける。
元の場所では、その名前をテーブルのように使う。
これを繰り返していくと、「あ、ここは“フィルタの段階”」「ここは“集計の段階”」と、
自分のSQLの中に“構造”が見えてきます。
CTEは、ただの機能ではなく、「自分のSQL思考を整理するための道具」です。
Day8のうちに、「1つでいいから、自分のSQLをCTEでリファクタリングしてみる」イメージを持っておいてほしいなと思います。
Day8 後半のまとめ
CTE(WITH句)は、「1つのSQLの中にある“段階”を上に引き上げて名前を付ける」ための仕組みであり、直近30日注文→高額ユーザー→対象ドメインユーザーのような処理を recent_orders・rich_users・target_users といった名前付きCTEに分けることで、「SQL全体のストーリー」を上から順に読める形にできる。
CTE名には“仕様書に出てきそうな名詞”をそのまま使い、t1・t2のような意味のない名前を避けることで、SQLを見ただけで「何をしているか」が伝わるようになり、MySQLやSQLite時代のサブクエリ地獄をPostgreSQLでは“WITH句でリファクタリングする”という発想に切り替える。
パフォーマンスの細かい話は一旦置いておき、「まずはCTEで可読性の高いSQLを書く → 本当に遅くなったときだけEXPLAINを見て調整する」という順番を意識しつつ、既存の複雑なSQLをWITH句で分割し直す練習を通して、「CTEを使うかどうかを自分で選べる」状態に近づく――これが Day8 後半の着地点になる。
