比較述語 — =, >=, <= と AND を組み合わせて価格・在庫で絞り込む
比較述語(= , <> , < , > , <= , >=)はSQLの最も基本的な述語です。複数の条件は AND(すべて真)または OR(いずれか真)で連結します。
SELECT * FROM products WHERE price >= 10000 -- 下限(境界値を含む) AND price <= 100000 -- 上限(境界値を含む) AND stock >= 1; -- 在庫あり
products テーブルから、price が 10,000 以上 100,000 以下、かつ stock が 1 以上(在庫あり)の商品を取得してください。取得列は product_id, name, category, price, stock、price 降順で返してください。
| product_id | name | category | price | stock |
|---|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 | 50 |
| 2 | スマートブック Pro | PC | 128000 | 0 |
| 3 | タブレット Air | タブレット | 64800 | 30 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 | 100 |
| 5 | USBケーブル | アクセサリ | 980 | 200 |
| 6 | ゲーミングPC Pro | PC | 198000 | 5 |
| product_id | name | category | price | stock |
|---|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 | 50 |
| 3 | タブレット Air | タブレット | 64800 | 30 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 | 100 |
SELECT product_id, name, category, price, stock FROM products WHERE price >= 10000 -- 下限:10,000円以上(境界値を含む) AND price <= 100000 -- 上限:100,000円以下(境界値を含む) AND stock >= 1 -- 在庫あり(stock が 0 より大きい) ORDER BY price DESC; /* 実行順序(SQLの論理的な評価順): 1. FROM products → 6行読み込み 2. WHERE price / stock の範囲条件 → 3行に絞り込み 3. SELECT product_id, name, ... → 5列を選択 4. ORDER BY price DESC → 価格降順に並び替え */
LEGEND
① FROM products
FROM productsproductsテーブル全6行を読み込みます。これがWHERE句で評価される入力データです。| product_id | name | price | stock |
|---|---|---|---|
| 1 | スマートフォン X | 89800 | 50 |
| 2 | スマートブック Pro | 128000 | 0 |
| 3 | タブレット Air | 64800 | 30 |
| 4 | ワイヤレスイヤホン | 12800 | 100 |
| 5 | USBケーブル | 980 | 200 |
| 6 | ゲーミングPC Pro | 198000 | 5 |
>= は境界値を含む(10000以上 → 10000も対象)、> は含まない(10000超 → 10001以上が対象)。仕様書の「以上・超・以下・未満」を正確に読むことが重要です。price >= 10000 AND price <= 100000 は price BETWEEN 10000 AND 100000 と等価です。BETWEEN は両端を含みます(Q2で詳解)。WHERE category = 'PC' AND category = 'タブレット' は「PCかつタブレット」を意味し1列が同時に2値を持つことはないため常に0件。カテゴリの複数指定には OR または IN を使います(Q5参照)。WHERE stock = NULL は UNKNOWN となり常に0件です。NULL判定には必ず IS NULL を使います(Q4で詳解)。stock >= 1 のように大半の行がTRUEになる低selectivity条件はインデックス効果が低く、オプティマイザがフルスキャンを選ぶ場合があります。BETWEEN述語 — 日付範囲指定と TIMESTAMP 型での境界値の罠
BETWEEN a AND b は col >= a AND col <= b と等価で、両端の境界値を含む範囲指定です。数値・日付・文字列のすべてに使えます。
SELECT * FROM orders WHERE ordered_at BETWEEN '2024-01-01' AND '2024-03-31'; -- ↑ ordered_at >= '2024-01-01' AND ordered_at <= '2024-03-31' と等価
TIMESTAMP 型の場合、'2024-03-31' は '2024-03-31 00:00:00' と解釈されます。3月31日 00:01 以降の行が除外されるため、TIMESTAMP型には ordered_at < '2024-04-01' を使うほうが安全です。orders テーブルから、ordered_at が 2024年1月1日〜2024年3月31日(Q1期間)の注文を取得してください。取得列は order_id, user_id, amount, status, ordered_at、ordered_at 昇順で返してください。
| order_id | user_id | amount | status | ordered_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | completed | 2024-01-15 |
| 102 | 2 | 3500 | completed | 2024-02-20 |
| 103 | 1 | 12000 | pending | 2024-04-05 |
| 104 | 3 | 9500 | completed | 2023-12-01 |
| 105 | 4 | 6000 | cancelled | 2024-03-10 |
| 106 | 3 | 4500 | completed | 2024-01-28 |
| order_id | user_id | amount | status | ordered_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | completed | 2024-01-15 |
| 106 | 3 | 4500 | completed | 2024-01-28 |
| 102 | 2 | 3500 | completed | 2024-02-20 |
| 105 | 4 | 6000 | cancelled | 2024-03-10 |
SELECT order_id, user_id, amount, status, ordered_at FROM orders WHERE ordered_at BETWEEN '2024-01-01' AND '2024-03-31' -- ↑ >= '2024-01-01' AND <= '2024-03-31' と等価 -- DATE型なら両端を含む。TIMESTAMP型は '2024-03-31 00:00:00' が上限になる点に注意。 ORDER BY ordered_at; /* 実行順序(SQLの論理的な評価順): 1. FROM orders → 6行読み込み 2. WHERE ordered_at BETWEEN ... → 4行に絞り込み 3. SELECT order_id, user_id, amount, ... → 5列を選択 4. ORDER BY ordered_at → 日付昇順 */
LEGEND
① FROM orders
FROM ordersordersテーブル全6行を読み込みます。ordered_at の値に注目してください。2023年・2024年Q1・3グループが混在しています。| order_id | user_id | amount | status | ordered_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | completed | 2024-01-15 |
| 102 | 2 | 3500 | completed | 2024-02-20 |
| 103 | 1 | 12000 | pending | 2024-04-05 |
| 104 | 3 | 9500 | completed | 2023-12-01 |
| 105 | 4 | 6000 | cancelled | 2024-03-10 |
| 106 | 3 | 4500 | completed | 2024-01-28 |
BETWEEN a AND b は col >= a AND col <= b と完全に等価です。a と b の境界値もヒットすることを必ず覚えましょう。'2024-03-31' は '2024-03-31 00:00:00' として解釈されます。3月31日 00:01 以降の注文が漏れるバグが起きます。TIMESTAMP型には ordered_at < '2024-04-01' パターンを使いましょう。NOT BETWEEN a AND b は col < a OR col > b と等価で、「範囲外の行を取得する」逆条件を書けます。BETWEEN 100000 AND 10000 は数学的に成立しないため常に0件。必ず 小さい値 AND 大きい値 の順で書きます。BETWEEN '2024-01-01' AND '2024-03-31' でタイムスタンプ型カラムの3月31日分が漏れるバグが頻発します。< '2024-04-01' に変えるだけで解決します。>= '月初' AND < '翌月初' パターンが実務標準です(例:2月は >= '2024-02-01' AND < '2024-03-01')。うるう年も自動的に考慮され、時刻の切り捨てが不要なため最も安全です。BETWEENは DATE 型カラムに限定して使うのが無難です。LIKE述語 — ワイルドカードによるパターンマッチング(前方・後方・中間一致)
LIKE述語は文字列のパターンマッチングに使います。%(パーセント)は0文字以上の任意文字列、_(アンダースコア)は任意の1文字に一致します。
WHERE name LIKE 'スマート%' -- 前方一致:「スマート」で始まる OR name LIKE '%Pro' -- 後方一致:「Pro」で終わる OR name LIKE '%Air%' -- 中間一致:「Air」を含む(インデックス不可) OR name LIKE '__ブレット'; -- _は任意の1文字(2文字+ブレット)
'prefix%' はB-treeインデックスが効きます。後方一致 '%suffix' と中間一致 '%word%' はインデックスが使えずフルスキャンになります。大規模テーブルでは要注意です。products テーブルから、name が「スマート」で始まる商品、またはname が「Pro」で終わる商品を取得してください。取得列は product_id, name, category, price、price 昇順で返してください。
| product_id | name | category | price |
|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 |
| 2 | スマートブック Pro | PC | 128000 |
| 3 | タブレット Air | タブレット | 64800 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 |
| 5 | USBケーブル | アクセサリ | 980 |
| 6 | ゲーミングPC Pro | PC | 198000 |
| product_id | name | category | price |
|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 |
| 2 | スマートブック Pro | PC | 128000 |
| 6 | ゲーミングPC Pro | PC | 198000 |
SELECT product_id, name, category, price FROM products WHERE name LIKE 'スマート%' -- 前方一致:先頭が「スマート」 OR name LIKE '%Pro' -- 後方一致:末尾が「Pro」 ORDER BY price; /* 実行順序(SQLの論理的な評価順): 1. FROM products → 6行読み込み 2. WHERE name LIKE ... OR ... → 3行に絞り込み 3. SELECT product_id, name, ... → 4列を選択 4. ORDER BY price → 価格昇順 */
LEGEND
① FROM products
FROM productsproductsテーブル全6行を読み込みます。name列のパターンに注目してください。| product_id | name | category | price |
|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 |
| 2 | スマートブック Pro | PC | 128000 |
| 3 | タブレット Air | タブレット | 64800 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 |
| 5 | USBケーブル | アクセサリ | 980 |
| 6 | ゲーミングPC Pro | PC | 198000 |
%(0文字以上の任意文字列)と _(任意の1文字)の2種類があります。例:'A_C' は「ABC」「AXC」に一致しますが、「AC」「ABBC」には不一致。'prefix%' はB-treeインデックスが使えます。後方一致 '%suffix' と中間一致 '%word%' はインデックスが効かずフルスキャンになります。全文検索が必要な場合は pg_trgm(PostgreSQL)や FULLTEXT INDEX(MySQL)の利用を検討しましょう。WHERE name NOT LIKE '%Pro' で「Proで終わらない商品」を抽出できます。複数パターンを除外したい場合は NOT LIKE '...' AND NOT LIKE '...' と AND でつなぎます。LIKE 'スマートフォン X' は = 'スマートフォン X' と等価ですが、インデックスの効き方が異なりオプティマイザが最適化しにくい場合があります。完全一致には = を使いましょう。LIKE '%keyword%' は数百万行のテーブルに対してはフルスキャンになります。運用テーブルへの実行前に EXPLAIN で確認し、必要なら全文検索インデックスを検討してください。% や _ がそのまま含まれるとパターンが意図せず広がります。アプリ側で % → %、_ → _ にエスケープし、LIKE :q ESCAPE '' と ESCAPE 句を使うのが正しい実装です。プレースホルダを使わずに文字列を直接連結するとSQLインジェクションの危険があります。IS NULL述語 — 三値論理と NULL 判定の正しい書き方
SQLは TRUE / FALSE の2値ではなく TRUE / FALSE / UNKNOWN の三値論理で動きます。NULL は「不明な値」であり、NULL = NULL も NULL <> NULL もすべて UNKNOWN になります。NULLの判定には必ず IS NULL / IS NOT NULL を使います。
-- ✗ 間違い:NULLに = は使えない(結果は常にUNKNOWN → 0件) WHERE coupon_code = NULL -- ✓ 正しい:IS NULL で判定する WHERE coupon_code IS NULL -- ✓ NULLでない行を取得 WHERE coupon_code IS NOT NULL
orders テーブルから、coupon_code が NULL(クーポン未使用)の注文を取得してください。取得列は order_id, user_id, amount, coupon_code, ordered_at、order_id 昇順で返してください。
| order_id | user_id | amount | coupon_code | ordered_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | SUMMER10 | 2024-06-01 |
| 102 | 2 | 12000 | NULL | 2024-06-03 |
| 103 | 3 | 5000 | NULL | 2024-06-05 |
| 104 | 1 | 9800 | WINTER20 | 2024-06-07 |
| 105 | 4 | 3200 | NULL | 2024-06-10 |
| order_id | user_id | amount | coupon_code | ordered_at |
|---|---|---|---|---|
| 102 | 2 | 12000 | NULL | 2024-06-03 |
| 103 | 3 | 5000 | NULL | 2024-06-05 |
| 105 | 4 | 3200 | NULL | 2024-06-10 |
SELECT order_id, user_id, amount, coupon_code, ordered_at FROM orders WHERE coupon_code IS NULL -- NULL判定は必ず IS NULL(= NULL は常にUNKNOWN) ORDER BY order_id; /* 実行順序(SQLの論理的な評価順): 1. FROM orders → 5行読み込み 2. WHERE coupon_code IS NULL → 3行に絞り込み 3. SELECT order_id, user_id, ... → 5列を選択 4. ORDER BY order_id → order_id 昇順 ▸ 三値論理メモ: 'SUMMER10' IS NULL → FALSE → 除外 NULL IS NULL → TRUE → 通過 NULL = NULL → UNKNOWN → 除外(これがよくある間違い) */
LEGEND
① FROM orders
FROM ordersordersテーブル全5行を読み込みます。coupon_code列にNULLと文字列が混在しています。| order_id | user_id | amount | coupon_code | ordered_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | SUMMER10 | 2024-06-01 |
| 102 | 2 | 12000 | NULL | 2024-06-03 |
| 103 | 3 | 5000 | NULL | 2024-06-05 |
| 104 | 1 | 9800 | WINTER20 | 2024-06-07 |
| 105 | 4 | 3200 | NULL | 2024-06-10 |
IS NULL は唯一NULLを正しく検出できる述語です。IS NOT NULL は「値が存在する行」を取得します。COALESCE関数と組み合わせて COALESCE(coupon_code, 'なし') のようにNULLをデフォルト値に変換する書き方も頻出です。WHERE col IS NOT NULL を内側に追加しましょう。col = NULL は常に UNKNOWN → 0件。必ず IS NULL を使います。IS NOT NULL で取得します。IN述語(固定リスト) — 複数値を OR なしにスマートに絞り込む
WHERE col IN (v1, v2, …) は col = v1 OR col = v2 OR … の省略形です。値を列挙してシンプルに複数一致を表現できます。
WHERE category IN ('PC', 'タブレット') -- ↑ category = 'PC' OR category = 'タブレット' と等価
products テーブルから、category が 'スマートフォン' または 'タブレット' または 'PC'の商品を取得してください。取得列は product_id, name, category, price, stock、category 昇順 → price 昇順で返してください。
| product_id | name | category | price | stock |
|---|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 | 50 |
| 2 | スマートブック Pro | PC | 128000 | 0 |
| 3 | タブレット Air | タブレット | 64800 | 30 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 | 100 |
| 5 | USBケーブル | アクセサリ | 980 | 200 |
| 6 | ゲーミングPC Pro | PC | 198000 | 5 |
| product_id | name | category | price | stock |
|---|---|---|---|---|
| 2 | スマートブック Pro | PC | 128000 | 0 |
| 6 | ゲーミングPC Pro | PC | 198000 | 5 |
| 1 | スマートフォン X | スマートフォン | 89800 | 50 |
| 3 | タブレット Air | タブレット | 64800 | 30 |
SELECT product_id, name, category, price, stock FROM products WHERE category IN ('スマートフォン', 'タブレット', 'PC') ORDER BY category, price; -- ↑ category = 'スマートフォン' OR category = 'タブレット' OR category = 'PC' と等価 /* 実行順序(SQLの論理的な評価順): 1. FROM products → 6行読み込み 2. WHERE category IN (...) → 4行に絞り込み 3. SELECT product_id, name, ... → 5列を選択 4. ORDER BY category, price → カテゴリ昇順→価格昇順 */
LEGEND
① FROM products
FROM productsproductsテーブル全6行を読み込みます。category列の値のバリエーションに注目してください。| product_id | name | category | price |
|---|---|---|---|
| 1 | スマートフォン X | スマートフォン | 89800 |
| 2 | スマートブック Pro | PC | 128000 |
| 3 | タブレット Air | タブレット | 64800 |
| 4 | ワイヤレスイヤホン | アクセサリ | 12800 |
| 5 | USBケーブル | アクセサリ | 980 |
| 6 | ゲーミングPC Pro | PC | 198000 |
IN (v1, v2, v3) は = v1 OR = v2 OR = v3 と等価です。値が増えても IN のリストを追加するだけで済み、OR の羅列より可読性が高くなります。WHERE category NOT IN ('アクセサリ') は「アクセサリ以外の商品」を取得できます。ただし NOT IN のリストに NULL が含まれると全件0件になる罠があるため注意が必要です(Q6で詳解)。IN (SELECT category FROM ... のようにサブクエリを使うと、テーブルの状態に応じて動的にリストを生成できます(Q6で詳解)。IN (id1, id2, ... id50000) のような巨大リストをSQLに埋め込むと、パース・評価コストが激増します。一時テーブルや JOIN を使う設計に変えましょう。IN (col1, col2) は列のリストではなく値リストです。複数列の組み合わせ一致には (col1, col2) IN ((v1, v2), (v3, v4)) の行値コンストラクタ構文(PostgreSQL対応)を使います。WHERE category = ANY($1)(PostgreSQL)や WHERE category IN (?)(複数バインド)として動的に組み立てます。値の数が変動しても安全に対応できます。