WITH 句(共通表式・CTE)の使い方
shain元の表
| shain_id | name | salary |
|---|---|---|
| 1 | 鈴木 | 980000 |
| 2 | 佐藤 | 720000 |
| 3 | 田中 | 520000 |
| … | ||
heikinWITH で作った表
| h |
|---|
| 542003 |
結果平均より上の人
| name | salary |
|---|---|
| 鈴木 | 980000 |
| 佐藤 | 720000 |
| 山田 | 700000 |
| … | |
- 元の表 18 行 → WITH で作った表 1 行 → 平均より上の人 5 行
- 前の段の結果が次の段の入力になります。表が 1 つ増えたつもりで読んでください
真ん中の heikin が WITH で作った表です。1 行 1 列しかありませんが、これも立派な表として本体から参照できます。
WITH は問い合わせに名前を付けて、そのあとの本体から表として使う書き方です。 共通表式(CTE)とも呼びます。同じ集計を 2 か所で使うとき、段階を分けて上から読めるようにするときに効きます。
主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01先に名前を付けてから本体を書く
結論: WITH 名前 AS (問い合わせ) 本体 の形です。 本体からは、その名前をふつうの表のように使えます。
読む順番が「材料 → 本体」になるのが特徴です。派生表は FROM の中に問い合わせが入り込むので、外側を読むために内側を先に読む必要がありますが、WITH なら上から順に読めます。
名前はカンマで区切っていくつでも定義できます。あとの定義から前の定義を参照することもできます。
⚠️ WITH で作った表は、その文の中だけのものです。文が終われば消えるので、表を作る(CREATE TABLE)のとは違います。
WITH zaiseki AS (★ここで名前を付けるSELECT * FROM shain中身はふつうの問い合わせWHERE taishoku_on IS NULL)在籍中だけSELECT b.busho_name, count(*) AS ninzu★ここから本体FROM zaiseki z付けた名前を表として使うJOIN busho b ON b.busho_id = z.busho_id結合の相手にもできるGROUP BY b.busho_nameまとめるORDER BY count(*) DESC, b.busho_name;結果の並べ方
上から読めば「在籍中の人を zaiseki と呼ぶ。その zaiseki を部署ごとに数える」と、そのまま日本語になります。
WITH zaiseki AS (
SELECT * FROM shain WHERE taishoku_on IS NULL
)
SELECT b.busho_name, count(*) AS ninzu
FROM zaiseki z
JOIN busho b ON b.busho_id = z.busho_id
GROUP BY b.busho_name
ORDER BY count(*) DESC, b.busho_name;| busho_name | ninzu |
|---|---|
| 開発 | 4 |
| 営業 | 3 |
| マーケ | 2 |
| 基盤 | 2 |
| 管理 | 2 |
| 経営 | 1 |
(6 行)
6 行です。派生表で書いた場合とまったく同じ結果で、違うのは読む順番だけです。
02同じ結果を 2 か所で使える
結論: WITH の本当の効きどころは、同じ結果を 2 回以上使うときです。
派生表は書いた場所でしか使えないので、2 か所で必要なら同じ SQL を 2 回書くことになります。片方だけ直して食い違う事故が起きます。
WITH なら定義は 1 か所です。直す場所が 1 つなので、食い違いようがありません。
⚠️ 「2 回書いてあるのは同じものか」を読む人に確かめさせないで済む、というのが本質です。速度の話ではありません。
WITH heikin AS (
SELECT round(avg(salary)) AS h FROM shain
)
SELECT (SELECT h FROM heikin) AS zentai_heikin,
count(*) AS ue_no_ninzu
FROM shain
WHERE salary > (SELECT h FROM heikin);| zentai_heikin | ue_no_ninzu |
|---|---|
| 542003 | 5 |
(1 行)
平均 542003 円を上回るのは 5 人でした。平均を出す式は 1 回しか書いていないので、片方だけ直してずれる心配がありません。
⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表
03段階を分けて上から読めるようにする
結論: 名前をいくつも定義して、処理を段に分けられます。
1 つの SELECT に副問い合わせを何重にも入れると、内側から読まないと分からない文になります。WITH で段に分ければ、上から順に「これを作って、次にこれを作って、最後にこう出す」と読めます。
名前の付け方がそのまま説明になるので、コメントを書かなくても意図が伝わるのが利点です。
⚠️ 分けすぎると今度は行き来が増えます。1 つの段が 1 つの意味になるところで切ってください。
WITH zaiseki AS (
SELECT * FROM shain WHERE taishoku_on IS NULL
),
busho_goukei AS (
SELECT busho_id, sum(salary) AS goukei, count(*) AS ninzu
FROM zaiseki
GROUP BY busho_id
)
SELECT b.busho_name, g.ninzu, g.goukei
FROM busho_goukei g
JOIN busho b ON b.busho_id = g.busho_id
ORDER BY g.goukei DESC;| busho_name | ninzu | goukei |
|---|---|---|
| 開発 | 4 | 2215050 |
| 営業 | 3 | 1651000 |
| 管理 | 2 | 1105000 |
| 基盤 | 2 | 1078000 |
| 経営 | 1 | 980000 |
| マーケ | 2 | 903000 |
(6 行)
6 行です。あとの定義(busho_goukei)から前の定義(zaiseki)を参照しています。上から順に読めば、何をしているかが名前だけで分かります。
04⚠️ よくある間違い:中で付けなかった列名は、外から使えない
結論: WITH の外から使う列には、中で名前を付けてください。 count(*) のような式に名前を付けないまま外で ninzu と書くと、その名前の列は無いというエラーになります。
WITH で作った表の列名は、中の SELECT が出した名前がそのまま使われます。式には自動で名前が付きますが、それは外で書こうとした名前とは違います。外から見えるのは中で決まった名前だけです。
直し方は 2 つあります。
- 中で
ASを付ける …count(*) AS ninzu - 名前の後ろに列名を並べる …
WITH ninzu_hyo (busho_id, ninzu) AS (…)。中の列に左から順に名前が付きます
⚠️ 後ろに並べる書き方では、並べる数を中の列の数と揃えてください。 中の列より多く並べるとエラーになります。
WITH ninzu_hyo AS (
SELECT busho_id, count(*)
FROM shain
WHERE busho_id IS NOT NULL
GROUP BY busho_id
)
SELECT busho_id, ninzu
FROM ninzu_hyo
ORDER BY busho_id;エラーになります。中の count(*) の列には自動の名前が付いていて、ninzu という列はどこにもありません。
WITH ninzu_hyo (busho_id, ninzu) AS (
SELECT busho_id, count(*)
FROM shain
WHERE busho_id IS NOT NULL
GROUP BY busho_id
)
SELECT busho_id, ninzu
FROM ninzu_hyo
ORDER BY busho_id;| busho_id | ninzu |
|---|---|
| 1 | 1 |
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 7 | 2 |
(6 行)
中の 2 列に、左から busho_id・ninzu の名前が付きます。中で count(*) AS ninzu と書いても同じ結果です。
WITH ninzu_hyo (busho_id, ninzu, heikin) AS (
SELECT busho_id, count(*)
FROM shain
WHERE busho_id IS NOT NULL
GROUP BY busho_id
)
SELECT busho_id, ninzu
FROM ninzu_hyo
ORDER BY busho_id;中の列は 2 つなのに 3 つ並べたので、エラーになります。並べる数は中の列の数と揃えます。
05派生表とどちらを使うか
結論: 1 か所でしか使わないなら派生表、2 か所以上か段に分けたいなら WITH が目安です。
どちらでも書けることがほとんどなので、読みやすいほうを選んでください。
- 派生表 … その場に書いてある。短い処理なら目で追いやすい
WITH… 名前が付く。長い処理や、同じものを何度も使う場合に強い
⚠️ 速さは製品と状況によります。 「WITH は遅い」「派生表のほうが速い」といった一般論は、いまの製品では当てになりません。遅いと感じたら両方書いて実際に測ってください。
⚠️ WITH には再帰という別の使い方もあります。階層をたどるための書き方で、こちらは派生表では代用できません。
SELECT b.busho_name, count(*) AS ninzu
FROM (SELECT * FROM shain WHERE taishoku_on IS NULL) t
JOIN busho b ON b.busho_id = t.busho_id
GROUP BY b.busho_name
ORDER BY count(*) DESC, b.busho_name;| busho_name | ninzu |
|---|---|
| 開発 | 4 |
| 営業 | 3 |
| マーケ | 2 |
| 基盤 | 2 |
| 管理 | 2 |
| 経営 | 1 |
(6 行)
6 行で、WITH で書いた最初の例と完全に同じ結果です。違うのは名前が付いているかどうかだけです。
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- FROM 句の副問い合わせFROM の中に直接書く書き方です。
- 再帰 CTEWITH で階層をたどる書き方です。
- GROUP BY 句段の中でよく使うまとめる操作です。
- スカラー副問い合わせ値として使う副問い合わせはこちらです。
根拠(一次情報)
- PostgreSQL 18 マニュアル: WITH 句(共通表式)一次情報・確認 2026-10-01
- PostgreSQL 18 マニュアル: SELECT(WITH の位置)一次情報・確認 2026-10-01