欲しい行だけ取り出す
「未発送の注文を新しい順に 10 件取得する」。WHERE、ORDER BY、LIMIT の基本と、NULL がもたらす三値論理の落とし穴を体験します。
- 全行ではなく、条件に合う行だけを取り出すにはどう書くか
- 並び順と件数はどう指定するか
- NULL が入った列を条件にすると何が起きるか
実際のデータを確認: 注文テーブル
合計 50 件の注文データが存在します。shipped_at 列は商品の発送日時を表しており、まだ発送されていない注文は NULL になっています(第 1 章で学んだ「値が存在しない」状態です)。 今回のチケットで求められている「未発送の注文」とは、まさにこの列が NULL である行を指しています。
WHERE: 条件に合う行だけを残す
WHERE 条件式 を追加すると、その条件が真(TRUE)になる行だけを絞り込んで取得できます。 イメージとしては、「テーブルの全 50 行を上から 1 行ずつ走査し、条件に当てはまる行だけをピックアップする」処理を想像すると理解しやすいでしょう (実際にデータベース内部でどのように探しているかは、第 8 章や第二部で詳しく見ていきます)。
よく使われる条件の書き方を試してみましょう。それぞれの条件で取得件数がどう変わるかを確認してください。
文字列や日時の値はシングルクォート ' で囲みます(数値は囲む必要はありません)。 なお、日時の範囲指定では BETWEEN '2026-07-01' AND '2026-07-31' と書くよりも、ordered_at >= '2026-07-01' AND ordered_at < '2026-08-01' のように翌月 1 日未満とする書き方のほうが実務では安全です。 時刻を省略した '2026-07-31' は「7 月 31 日 00:00:00」と解釈されるため、7 月 31 日の日中に発生した注文が検索から漏れてしまうトラブルを防ぐためです。
ORDER BY と LIMIT: 並び順を指定して件数を制限する
ORDER BY 列名 DESC で降順(大きい順・新しい順)、ASC(省略時のデフォルト)で昇順(小さい順・古い順)に並び替えます。ORDER BY status, ordered_at DESC のようにカンマ区切りで複数の列を指定すると、第 1 条件で並べたあと、値が同じ行同士を第 2 条件で並べ替えることができます。LIMIT 10 は、指定した件数(ここでは先頭 10 件)で結果を打ち切る指定です。
SQL では、ORDER BY を明示的に書かなかった場合、結果の並び順は保証されません。 開発環境でたまたま id 順に並んで見えていたとしても、それは保証された挙動ではなく、テーブルの読み出し状況によっていつでも変わり得ます。 「見た目上並んでいるから」と ORDER BY を省略してしまうと、本番環境でデータ量が増えたり再起動したりした際に、予期せず表示順が変わる障害につながります。
OFFSET は「先頭から何件をスキップするか」を指定し、Web アプリケーションのページネーションでよく利用されます。ただし「スキップする行も内部的には読み取っている」という性質があり、これは第二部 6 章のパフォーマンス改善で重要なポイントになります。
注意すべき挙動: NULL は比較のどちら側にも入らない
第 1 章で「NULL は = では検索できない」ことを学びました。実は、比較演算に関してもう一段意外な挙動が存在します。 「7 月 1 日 00:00:00 ちょうどに発送された注文以外」を取得してみましょう。50 件の注文のうち、その日時に発送された注文は 1 件もないため、直感的には全 50 件がそのまま返ってくるように思えます。 何件返ってくるかを予測してから、クエリを実行してみてください。
結果は 50 件ではなく、47 件になったはずです。 除外された 3 件は shipped_at が NULL(未発送)の注文です。NULL と何かを <>(等しくない)で比較した結果も真偽値ではなく「不明(UNKNOWN)」となるため、WHERE 句の条件から落ちてしまうのです。NULL は、= でも <> でも、< や > であっても、どの比較演算においても「真」にはなりません。NULL を許容している列で「〜以外」という条件を書くと、NULL の行が意図せず暗黙的に除外されてしまいます。NULL の行も含めたい場合は、OR shipped_at IS NULL を明示的に追加する必要があります。
これは、一般的なプログラミング言語の if 文(真か偽の 2 値)と大きく異なるデータベース固有の性質(3 値論理)です。 SQL 初学者が最もつまずきやすく、本番システムの集計で「一部のデータがなぜか集計結果から消えている」というバグの原因の定番でもあります。
DISTINCT: 重複を除外する
クエリを書く順番と、データベースが実行する順番
ここまでに登場した句を組み合わせると、クエリは以下の順序で記述します。
SELECT 列名 FROM テーブル名 WHERE 条件 ORDER BY 列名 LIMIT 件数
一方で、データベース内部で実際に処理される順番は記述順とは異なり、 FROM(取得元の特定)→ WHERE(対象行の絞り込み)→ SELECT(必要な列の選択)→ ORDER BY(並び替え)→ LIMIT(件数の切り出し) の順で処理されます。「まず条件で絞り込んでから、並べ替えて切り出す」と理解しておくと良いでしょう。
WHERE で絞り込み、ORDER BY で整列し、LIMIT で件数を制限します。条件判定は真・偽・不明の 3 値で扱われ、NULL は通常の比較では抽出されません。
確認
NULL を許した列に「〜以外」の条件を書くと、何が起きるか。
ある月の注文を取り出すとき、月末の日付を上限に書くと何を落とすか。
ORDER BY を書かない一覧画面が、ある日突然順番を変えるのはなぜか。
次の章へ
条件に合う行を柔軟に抽出できるようになりました。次のチケットでは「月ごとの注文件数」や「顧客ごとの購入数」といった集計処理に取り組みます。 単に行を取り出すだけでなく、データをグループ化して集計する GROUP BY の仕組みと、そこでの落とし穴を体験していきましょう。