コマンド道場

WITH 句(共通表式・CTE)の使い方

名前を付けた表を、本体から使う

shain元の表

shain_idnamesalary
1鈴木980000
2佐藤720000
3田中520000

heikinWITH で作った表

h
542003

結果平均より上の人

namesalary
鈴木980000
佐藤720000
山田700000
  • 元の表 18WITH で作った表 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)のとは違います。

1 行ずつ、何をしているか
  1. WITH zaiseki AS (★ここで名前を付ける
  2. SELECT * FROM shain中身はふつうの問い合わせ
  3. WHERE taishoku_on IS NULL)在籍中だけ
  4. SELECT b.busho_name, count(*) AS ninzu★ここから本体
  5. FROM zaiseki z付けた名前を表として使う
  6. JOIN busho b ON b.busho_id = z.busho_id結合の相手にもできる
  7. GROUP BY b.busho_nameまとめる
  8. 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_nameninzu
開発4
営業3
マーケ2
基盤2
管理2
経営1

6

6 行です。派生表で書いた場合とまったく同じ結果で、違うのは読む順番だけです。

02同じ結果を 2 か所で使える

結論: WITH の本当の効きどころは、同じ結果を 2 回以上使うときです。

派生表は書いた場所でしか使えないので、2 か所で必要なら同じ SQL を 2 回書くことになります。片方だけ直して食い違う事故が起きます。

WITH なら定義は 1 か所です。直す場所が 1 つなので、食い違いようがありません。

⚠️ 「2 回書いてあるのは同じものか」を読む人に確かめさせないで済む、というのが本質です。速度の話ではありません。

平均を 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_heikinue_no_ninzu
5420035

1

平均 542003 円を上回るのは 5 人でした。平均を出す式は 1 回しか書いていないので、片方だけ直してずれる心配がありません。

⚠️ この書き方は SQL Server / IBM Db2 では使えません(round(x)(引数 1 つ))。製品ごとの対応表

03段階を分けて上から読めるようにする

結論: 名前をいくつも定義して、処理を段に分けられます。

1 つの SELECT に副問い合わせを何重にも入れると、内側から読まないと分からない文になります。WITH で段に分ければ、上から順に「これを作って、次にこれを作って、最後にこう出す」と読めます。

名前の付け方がそのまま説明になるので、コメントを書かなくても意図が伝わるのが利点です。

⚠️ 分けすぎると今度は行き来が増えます。1 つの段が 1 つの意味になるところで切ってください。

2 つの名前を定義して段に分ける
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_nameninzugoukei
開発42215050
営業31651000
管理21105000
基盤21078000
経営1980000
マーケ2903000

6

6 行です。あとの定義(busho_goukei)から前の定義(zaiseki)を参照しています。上から順に読めば、何をしているかが名前だけで分かります。

04派生表とどちらを使うか

結論: 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_nameninzu
開発4
営業3
マーケ2
基盤2
管理2
経営1

6

6 行で、WITH で書いた最初の例と完全に同じ結果です。違うのは名前が付いているかどうかだけです。

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

関連するトピック

根拠(一次情報)