SQL Scale 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 条件式 を追加すると、その条件が真(TRUE)になる行だけを絞り込んで取得できます。 イメージとしては、「テーブルの全 50 行を上から 1 行ずつ走査し、条件に当てはまる行だけをピックアップする」処理を想像すると理解しやすいでしょう (実際にデータベース内部でどのように探しているかは、第 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' のように翌月 1 日未満とする書き方のほうが実務では安全です。 時刻を省略した '2026-07-31' は「7 月 31 日 00:00:00」と解釈されるため、7 月 31 日の日中に発生した注文が検索から漏れてしまうトラブルを防ぐためです。

ORDER BY と LIMIT: 並び順を指定して件数を制限する

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

ORDER BY 列名 DESC で降順(大きい順・新しい順)、ASC(省略時のデフォルト)で昇順(小さい順・古い順)に並び替えます。ORDER BY status, ordered_at DESC のようにカンマ区切りで複数の列を指定すると、第 1 条件で並べたあと、値が同じ行同士を第 2 条件で並べ替えることができます。LIMIT 10 は、指定した件数(ここでは先頭 10 件)で結果を打ち切る指定です。

SQL では、ORDER BY を明示的に書かなかった場合、結果の並び順は保証されません。 開発環境でたまたま id 順に並んで見えていたとしても、それは保証された挙動ではなく、テーブルの読み出し状況によっていつでも変わり得ます。 「見た目上並んでいるから」と ORDER BY を省略してしまうと、本番環境でデータ量が増えたり再起動したりした際に、予期せず表示順が変わる障害につながります。

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

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 初学者が最もつまずきやすく、本番システムの集計で「一部のデータがなぜか集計結果から消えている」というバグの原因の定番でもあります。

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:00偽真偽202026-07-02 19:22:00偽真偽1NULL不明不明真25NULL不明不明真31NULL不明不明真残る行数(50 行中)0 行47 行3 行
WHERE 句が残すのは条件が「真」の行のみです。NULL との比較は = でも <> でも「不明」となり、結果から除外されます。

DISTINCT: 重複を除外する

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

クエリを書く順番と、データベースが実行する順番

ここまでに登場した句を組み合わせると、クエリは以下の順序で記述します。

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

一方で、データベース内部で実際に処理される順番は記述順とは異なり、 FROM(取得元の特定)→ WHERE(対象行の絞り込み)→ SELECT(必要な列の選択)→ ORDER BY(並び替え)→ LIMIT(件数の切り出し) の順で処理されます。「まず条件で絞り込んでから、並べ替えて切り出す」と理解しておくと良いでしょう。

書く順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 を書く。

次の章へ

条件に合う行を柔軟に抽出できるようになりました。次のチケットでは「月ごとの注文件数」や「顧客ごとの購入数」といった集計処理に取り組みます。 単に行を取り出すだけでなく、データをグループ化して集計する GROUP BY の仕組みと、そこでの落とし穴を体験していきましょう。