クエリの中にクエリを書く
SELECT の結果は値にも集合にも表にもなる。IN / EXISTS / 派生テーブル / WITH と、NOT IN の罠。
前提: 第 5 章のあと npm run seed:basics で作り直してある。
- SELECT の結果を、別の SELECT の中で使うには
- IN と EXISTS はどう違うか
- NOT IN で行が全部消えるのはなぜか
実物: SELECT の結果は 3 通りの形で使える
括弧で囲んだ SELECT は、その結果の形によって、値・集合・表のどれかとして扱える。3 つを順に見る。
(SELECT AVG(unit_price) FROM order_items) は 1 行 1 列を返すので、1 つの値として > の右に置ける。 これをサブクエリと呼ぶ。「クエリの中のクエリ」だ。
(SELECT id FROM customers WHERE tier = 'gold') は id の集合(2 つの値)を返すので、IN の右に置ける。 第 4 章の JOIN でも同じ結果が出せる。顧客の列(名前など)を出すなら JOIN、顧客は絞り込みにだけ使って出さないならサブクエリ、と使い分けると意図が読みやすい。
FROM の中の SELECT は表として扱われ、別名(t)が必須になる。「集計した結果を、さらに集計する」ときの形だ。派生テーブルと呼ぶ。
EXISTS: 対応する行が 1 つでもあるか
括弧の中に c.id という外側の列が出てくる。「顧客 1 行ごとに、その顧客のキャンセル注文があるかを調べる」と読む。 外側の行を参照するサブクエリを相関サブクエリと呼ぶ。SELECT 1 は「何を返すかはどうでもよく、あるかないかだけ」の意味。
驚き: NOT IN が全部消す
「発送日時と同じ時刻に注文された注文はないはず」を確かめるため、注文日時が発送日時の集合に含まれない注文を数える。50 行返るはずだ。予測してから実行する。
0 のはずだ。50 ではない。shipped_at の集合に NULL が 3 つ入っている。x NOT IN (a, b, NULL) は x <> a AND x <> b AND x <> NULL で(これは意味の展開で、SQL にそう書くわけではない)、 最後が「不明」なので全体が不明になり、全行が落ちる(第 2 章の 3 値論理が、集合の中に潜んで出てきた)。集合の要素ごとの比較と AND のまとめ方を、注文 2 で描く。
NOT IN の中は NULL を除く。あるいは NOT EXISTS で書く。NOT EXISTS は「あるかないか」しか見ないので NULL の罠がない。 NULL を許した列に対しては NOT EXISTS を選ぶ人が多い。
WITH: サブクエリに名前を付ける
派生テーブルが入れ子になると読めない。WITH 名前 AS (…) で先に定義してから使う。
per_customer を 2 回使っている(JOIN と、平均の計算)。派生テーブルなら同じ SELECT を 2 回書く必要があった。 複数の WITH はカンマで並べ、後のものから前のものを参照できる。
括弧で囲んだ SELECT は、結果の形によって値・集合・表として使える。NOT IN の集合に NULL があると全行が落ちる。
確認
括弧で囲んだ SELECT が、値・集合・表のどれとして扱われるかは何で決まるか。
NULL を許した列に対して、NOT IN より NOT EXISTS が好まれるのはなぜか。
同じ集計を 2 か所で使うとき、派生テーブルより WITH が読みやすいのはなぜか。
次の章へ
次の「補足: 行を潰さずに集計する」は、飛ばしても第二部に影響しない。第 7 章では、注文を入れて在庫を減らす 2 つの文を「1 つの操作」にする方法を、 2 本の接続を交互に操作しながら体験する。