SQL Lab
第一部 DB 基礎編 · 第 2 章 · 約 30

欲しい行だけ取り出す

「未発送の注文を新しい順に 10 件」。WHERE / ORDER BY / LIMIT と、NULL の三値論理という最初の驚き。

この章で答える問い
  1. 全行ではなく、条件に合う行だけを取り出すにはどう書くか
  2. 並び順と件数はどう指定するか
  3. NULL が入った列を条件にすると何が起きるか

実物: 注文の表

orders を全部
期待: 50 行。shipped_at が NULL の行が 3 つ

50 行ある。shipped_at は発送日時で、まだ発送していない注文は NULL になっている(第 1 章の「値がない」)。 チケットの「未発送」は、この列が NULL の行のことだ。

WHERE: 条件に合う行だけ残す

未発送の注文
期待: 3 行

WHERE 条件 を付けると、条件が真の行だけが残る。頭の中では「50 行を先頭から順に見て、条件に合う行を拾う」ループを想像すればいい (本当にそうやって探しているかは第 8 章で覗く)。

条件の書き方をいくつか試す。それぞれ、行数を見る。

等しい
期待: 40 行
どれかに等しい(IN)
期待: 8 行
大小(日時も比べられる)
期待: 16 行。8 月の注文
AND / OR で組み合わせる
期待: 行数は自分で確かめる。OR より AND が先に結ばれるので、迷ったら括弧
範囲(BETWEEN は両端を含む)
期待: 11 行
部分一致(% は任意の文字列)
期待: 2 行

文字列と日時は ' で囲む。数値は囲まない。日付の範囲は 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: 並べて、切る

新しい順
期待: 3 行が ordered_at の降順
最大 10 件(チケット完了)
期待: 3 行(10 件に満たないのでそのまま)

ORDER BY 列 DESC で降順、ASC(省略時)で昇順。ORDER BY status, ordered_at DESC のように複数書くと、 最初の列で並べ、同じ値の中を次の列で並べる。LIMIT 10 は先頭 10 行で打ち切る。

ORDER BY を書かなければ、行の順番は決まっていない。今日 id 順に見えても、それは偶然で、保証はない。 「並んで見えるから」と ORDER BY を省いた画面は、本番で行が増えたある日、順番が入れ替わる。

11 件目から 10 件(2 ページ目)
期待: 配達済み 40 件の、11〜20 件目

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 つの条件を当てた結果を並べる。

orders(50 行のうち 5 行)。右 3 列は shipped_at に各条件を当てた結果idshipped_at= '2026-07-01 00:00:00'<> '2026-07-01 00:00:00'IS NULL22026-06-06 09:42:00202026-07-02 19:22:001NULL不明不明25NULL不明不明31NULL不明不明残る行数(50 行中)0 行47 行3 行
WHERE が残すのは「真」の行だけ。NULL との比較は = でも <> でも「不明」になり、黙って落ちる。

DISTINCT: 重複を消す

会員種別の種類
期待: 3 行

書く順番と、動く順番

ここまでの部品を全部使うと、こう並べる。

SELECT 列
FROM テーブル
WHERE 条件
ORDER BY 列
LIMIT 件数

動く順番は少し違って、FROM(どこから)→ WHERE(絞る)→ SELECT(列を選ぶ)→ ORDER BY(並べる)→ LIMIT(切る)。 「絞ってから並べて切る」と覚える。次章で集計が入ると、この順番に GROUP BY と HAVING が挟まる。 上のページ送りのクエリで、書く順と動く順を並べる。

書く順3SELECT *1FROM orders2WHERE status = 'delivered'4ORDER BY ordered_at DESC5LIMIT 10 OFFSET 10動く順1 FROM50 行2 WHERE40 行3 SELECT40 行(* なので全列)4 ORDER BY40 行(並べ替え)5 LIMIT / OFFSET10 行(10 行飛ばして 10 行)
上のページ送りのクエリ。左の番号が右の動く順。WHERE で 50 行が 40 行に減り、並べ替えと LIMIT はその 40 行だけを相手にする。

WHERE で絞り、ORDER BY で並べ、LIMIT で切る。条件は真・偽・不明の 3 値で、NULL の行はどの比較にも入らない。

確認

NULL を許した列に「〜以外」の条件を書くと、何が起きるか。
NULL との比較は = でも <> でも「不明」になるので、その列が NULL の行は黙って落ちる。NULL の行も残したいなら OR 列 IS NULL を足す。
ある月の注文を取り出すとき、月末の日付を上限に書くと何を落とすか。
日付だけ書いた値はその日の 0 時を指すので、月末の日中の注文が落ちる。翌月 1 日より前、という書き方にすれば落ちない。
ORDER BY を書かない一覧画面が、ある日突然順番を変えるのはなぜか。
ORDER BY がなければ行の順番は決まっておらず、たまたま並んで見えているだけだから。順番に意味があるなら必ず ORDER BY を書く。

次の章へ

欲しい行は取り出せるようになった。次のチケットは「月ごとの注文数」と「顧客ごとの注文数」で、行を取り出すのではなく、数える必要がある。 そこで「顧客ごとに数えると、注文が 0 件の顧客が消える」という問題に出会う。