欲しい行だけ取り出す
「未発送の注文を新しい順に 10 件」。WHERE / ORDER BY / LIMIT と、NULL の三値論理という最初の驚き。
- 全行ではなく、条件に合う行だけを取り出すにはどう書くか
- 並び順と件数はどう指定するか
- NULL が入った列を条件にすると何が起きるか
実物: 注文の表
50 行ある。shipped_at は発送日時で、まだ発送していない注文は NULL になっている(第 1 章の「値がない」)。 チケットの「未発送」は、この列が NULL の行のことだ。
WHERE: 条件に合う行だけ残す
WHERE 条件 を付けると、条件が真の行だけが残る。頭の中では「50 行を先頭から順に見て、条件に合う行を拾う」ループを想像すればいい (本当にそうやって探しているかは第 8 章で覗く)。
条件の書き方をいくつか試す。それぞれ、行数を見る。
文字列と日時は ' で囲む。数値は囲まない。日付の範囲は BETWEEN '2026-07-01' AND '2026-07-31' よりordered_at >= '2026-07-01' AND ordered_at < '2026-08-01' のほうが安全だ。'2026-07-31' は「7 月 31 日の 0 時 0 分」なので、31 日の日中の注文が落ちる。
ORDER BY と LIMIT: 並べて、切る
ORDER BY 列 DESC で降順、ASC(省略時)で昇順。ORDER BY status, ordered_at DESC のように複数書くと、 最初の列で並べ、同じ値の中を次の列で並べる。LIMIT 10 は先頭 10 行で打ち切る。
ORDER BY を書かなければ、行の順番は決まっていない。今日 id 順に見えても、それは偶然で、保証はない。 「並んで見えるから」と ORDER BY を省いた画面は、本番で行が増えたある日、順番が入れ替わる。
OFFSET は「先頭から何行読み飛ばすか」。ページ送りに使う。読み飛ばす分も読んでいる、という点は第二部 6 章で効いてくる。
驚き: NULL は比較のどちら側にも入らない
第 1 章で「NULL は = では見つからない」を見た。もう一段意外なことが起きる。 「7 月 1 日 0 時ちょうどに発送された注文以外」を取り出す。50 件のうち、7 月 1 日 0 時ちょうどの発送はないので、全部残るはずに思える。 行数を予測してから実行する。
47 行のはずだ。50 行ではない。消えた 3 行は shipped_at が NULL の注文で、NULL と何かを <> で比べた結果も「不明」なので落ちる。NULL は、= でも <> でも、< でも > でも、どちら側にも入らない。「発送済みでない注文」を status <> 'delivered' のように書くのは正しい(status は NOT NULL なので)が、 NULL を許した列で「〜以外」を書くと、NULL の行が黙って消える。含めたいなら OR shipped_at IS NULL を足す。
これは、配列とループの世界にはない性質だ。ループの中の if は真か偽のどちらかになるが、SQL の条件は真・偽・不明の 3 値を取る。 DB を初めて触る人が最初に踏む罠で、本番で「集計から一部の行が消えている」原因の定番でもある。 発送済み 2 行と未発送 3 行の計 5 行に、3 つの条件を当てた結果を並べる。
DISTINCT: 重複を消す
書く順番と、動く順番
ここまでの部品を全部使うと、こう並べる。
SELECT 列 FROM テーブル WHERE 条件 ORDER BY 列 LIMIT 件数
動く順番は少し違って、FROM(どこから)→ WHERE(絞る)→ SELECT(列を選ぶ)→ ORDER BY(並べる)→ LIMIT(切る)。 「絞ってから並べて切る」と覚える。次章で集計が入ると、この順番に GROUP BY と HAVING が挟まる。 上のページ送りのクエリで、書く順と動く順を並べる。
WHERE で絞り、ORDER BY で並べ、LIMIT で切る。条件は真・偽・不明の 3 値で、NULL の行はどの比較にも入らない。
確認
NULL を許した列に「〜以外」の条件を書くと、何が起きるか。
ある月の注文を取り出すとき、月末の日付を上限に書くと何を落とすか。
ORDER BY を書かない一覧画面が、ある日突然順番を変えるのはなぜか。
次の章へ
欲しい行は取り出せるようになった。次のチケットは「月ごとの注文数」と「顧客ごとの注文数」で、行を取り出すのではなく、数える必要がある。 そこで「顧客ごとに数えると、注文が 0 件の顧客が消える」という問題に出会う。