SQLを勉強していて、しばらく苦手だったものがあります。
それが、WITH句です。
WITH sales_summary AS (
SELECT
product_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY product_id
)
SELECT *
FROM sales_summary;
最初にこの書き方を見たとき、正直よく分かりませんでした。
SELECT文より前に何か書いてある。
テーブルを作っているようにも見える。
サブクエリと何が違うのかも分からない。
しかも、実務で見るSQLにはWITH句がいくつも並んでいて、読むだけで頭が止まっていました。
しかし、ある考え方をするようになってから、WITH句への苦手意識がかなり減りました。
それは、
「WITH句は途中結果に名前を付けるためのもの」
だと考えることです。
WITH句は一時的なメモのようなもの
WITH句は、正式には共通テーブル式、CTEと呼ばれます。
ただ、最初からその言葉で理解しようとすると少し難しく感じます。
私はまず、
「SQLの中だけで使える一時的なメモ」
くらいに考えると理解しやすくなりました。
例えば、以下のような売上テーブルがあるとします。
| order_id | product_id | amount |
|---|---|---|
| 1 | A | 1000 |
| 2 | A | 2000 |
| 3 | B | 1500 |
商品ごとの売上合計を出したい場合、まず商品ごとに集計します。
SELECT
product_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY product_id
この結果に、仮の名前を付けておく。
それがWITH句です。
WITH sales_summary AS (
SELECT
product_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY product_id
)
SELECT *
FROM sales_summary;
sales_summaryというテーブルが実際に作成されるわけではありません。
このSQLを実行している間だけ、
「この集計結果をsales_summaryと呼ぶことにする」
というイメージです。
サブクエリとの違い
WITH句が分かりにくい理由のひとつは、サブクエリでも似たようなことができる点です。
例えば、先ほどのSQLはサブクエリでも書けます。
SELECT *
FROM (
SELECT
product_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY product_id
) AS sales_summary;
これでも結果は同じです。
では、WITH句を使う意味は何でしょうか。
それは、SQLを読みやすくするためです。
サブクエリは、処理が複雑になるほど内側にどんどん深くなっていきます。
SELECT ...
FROM (
SELECT ...
FROM (
SELECT ...
FROM sales
) AS step1
) AS step2;
こうなると、どこから読めばいいのか分かりにくくなります。
一方、WITH句を使うと処理を段階ごとに分けられます。
WITH step1 AS (
...
),
step2 AS (
...
),
step3 AS (
...
)
SELECT *
FROM step3;
途中結果に名前が付くので、SQL全体の流れを追いやすくなります。
WITH句は「上から順に処理を分ける」ために使う
WITH句を理解するうえで大事なのは、完成形のSQLをいきなり読もうとしないことです。
私は、WITH句を以下のような作業手順だと考えるようにしました。
- まず元データを整える
- 次に集計する
- 必要な条件で絞る
- 最後に表示する
例えば、月次売上を集計して、売上が一定以上の商品だけを表示したいとします。
WITH cleaned_sales AS (
SELECT
product_id,
order_date,
amount
FROM sales
WHERE amount IS NOT NULL
),
monthly_sales AS (
SELECT
product_id,
DATE_TRUNC(order_date, MONTH) AS sales_month,
SUM(amount) AS total_amount
FROM cleaned_sales
GROUP BY
product_id,
DATE_TRUNC(order_date, MONTH)
),
filtered_sales AS (
SELECT
*
FROM monthly_sales
WHERE total_amount >= 100000
)
SELECT *
FROM filtered_sales;
このSQLは、いきなり全部を理解しようとすると少し難しく見えます。
でも、分解するとかなりシンプルです。
cleaned_salesでは、まず売上データを整えています。
monthly_salesでは、月別に集計しています。
filtered_salesでは、一定以上の売上に絞っています。
最後に、その結果を表示しています。
つまりWITH句は、複雑な処理を小さなステップに分けるための道具です。
名前の付け方がかなり大事
WITH句を使うときは、名前の付け方がとても重要です。
例えば、以下のような名前だと読みづらくなります。
WITH t1 AS (...),
t2 AS (...),
t3 AS (...)
短くて書きやすいですが、後から読んだときに何をしているのか分かりません。
一方で、以下のように名前を付けると読みやすくなります。
WITH raw_sales AS (...),
monthly_sales AS (...),
high_value_products AS (...)
名前を見るだけで、処理の役割が分かります。
WITH句は処理を分けるだけでなく、SQLに説明を加える役割もあります。
実務ではどんなときに使うのか
実務では、WITH句はかなり頻繁に使います。
例えば、
- 売上データを月次で集計する
- 会員ごとの初回購入日を取得する
- 商品別・店舗別のKPIを作る
- TableauやBIツール用のデータマートを作る
- GA4や広告データを分析しやすい形に整える
といった場面です。
特にBIツールに渡す前のデータ加工では、1本のSQLの中で複数の処理を行うことがあります。
そのとき、WITH句を使わないとSQLが非常に読みにくくなります。
WITH句が苦手な人は、まず1つだけ使えばいい
WITH句が苦手な人は、最初から複数のWITH句を書こうとしなくて大丈夫です。
まずは1つだけ使ってみるのがおすすめです。
例えば、
WITH sales_summary AS (
SELECT
product_id,
SUM(amount) AS total_amount
FROM sales
GROUP BY product_id
)
SELECT *
FROM sales_summary;
これだけでも十分です。
慣れてきたら、
WITH step1 AS (...),
step2 AS (...)
SELECT *
FROM step2;
のように、少しずつ増やしていけば問題ありません。
私が理解できたポイント
私がWITH句を理解できたポイントは、
「WITH句は難しいSQLを書くためのものではなく、SQLを読みやすくするためのもの」
だと考えたことでした。
以前は、WITH句が出てくると身構えていました。
でも今は、
「途中結果に名前を付けて、処理を整理しているだけ」
と考えています。
そう思うと、かなり気が楽になります。
まとめ
WITH句は、最初は少し分かりにくい構文です。
しかし、本質はとてもシンプルです。
WITH句は、途中結果に名前を付けるためのものです。
サブクエリで書ける処理を、より読みやすく整理するために使います。
複雑なSQLを一気に理解しようとするのではなく、
「このWITH句では何の途中結果を作っているのか」
をひとつずつ見ていくと、かなり読みやすくなります。
WITH句が苦手な人は、まずはこう考えてみてください。
SQLの中で、一時的なメモを作っているだけ。
そう思えるようになると、WITH句は少し怖くなくなります。