LIKE述語 × EXISTS述語 — パターンマッチと存在確認を組み合わせた実践検索
LIKE述語は文字列のパターン一致を検査します。%(任意の0文字以上)と _(任意の1文字)の2種類のワイルドカードがあります。EXISTS と組み合わせることで「名前パターン × 購買実績あり」のような実務頻出の複合フィルタを実現できます。
-- % : 0文字以上の任意文字列 WHERE name LIKE '田%' -- 「田」で始まる(前方一致)※インデックス有効 WHERE name LIKE '%田%' -- 「田」を含む(中間一致)※インデックス不可 -- _ : 任意の1文字 WHERE code LIKE 'A_01' -- A?01 形式(?は任意1文字) -- ILIKE: 大文字小文字を区別しない(PostgreSQL固有) WHERE email ILIKE '%@example.com'
users テーブルから、name が '田' で始まる(LIKE '田%')かつ status が 'completed' の注文が1件以上あるユーザーを取得してください。取得列は user_id, name, email、user_id 昇順で返してください。
| user_id | name | plan | |
|---|---|---|---|
| 1 | 田中 太郎 | tanaka@example.com | premium |
| 2 | 佐藤 花子 | sato@example.com | standard |
| 3 | 田村 一郎 | tamura@example.com | free |
| 4 | 山田 次郎 | yamada@example.com | premium |
| 5 | 伊藤 三郎 | ito@example.com | premium |
| order_id | user_id | status |
|---|---|---|
| 101 | 1 | completed |
| 102 | 2 | completed |
| 103 | 3 | completed |
| 104 | 3 | completed |
| 105 | 4 | cancelled |
| 106 | 5 | pending |
| user_id | name | |
|---|---|---|
| 1 | 田中 太郎 | tanaka@example.com |
| 3 | 田村 一郎 | tamura@example.com |
BETWEEN述語 × NOT EXISTS述語 — 価格帯フィルタと未販売商品の検出
BETWEEN は範囲条件を簡潔に書ける述語です。a BETWEEN x AND y は a >= x AND a <= y(両端を含む閉区間)と等価です。NOT EXISTS と組み合わせることで「価格帯 × 売上実績なし」のような在庫・販売分析クエリを実現できます。
-- BETWEEN: 両端(x, y)を含む閉区間 WHERE price BETWEEN 3000 AND 7000 -- >= 3000 AND <= 7000 と等価 -- NOT BETWEEN: 範囲外 WHERE price NOT BETWEEN 3000 AND 7000 -- < 3000 OR > 7000 と等価 -- 日付範囲での注意点(timestamp型) WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31' -- timestamp型では '2024-01-31 00:00:00' まで → 31日の注文が漏れる -- 正: created_at >= '2024-01-01' AND created_at < '2024-02-01'
>= start AND < end+1 形式が安全です。products テーブルから、category が 'アクセサリ' かつ price が 3,000〜7,000(BETWEEN)の商品のうち、completed の注文が1件もない商品を NOT EXISTS で取得してください。取得列は product_id, name, price, stock、price 昇順で返してください。
| product_id | name | category | price | stock |
|---|---|---|---|---|
| 1 | ワイヤレスマウス | アクセサリ | 3500 | 80 |
| 2 | メカニカルキーボード | アクセサリ | 8800 | 45 |
| 3 | HDMIケーブル 2m | アクセサリ | 1200 | 150 |
| 4 | USBハブ 7ポート | アクセサリ | 4200 | 30 |
| 5 | 外付けSSD 1TB | ストレージ | 9800 | 20 |
| 6 | Webカメラ HD | アクセサリ | 6500 | 15 |
| order_id | product_id | status |
|---|---|---|
| 101 | 4 | completed |
| 102 | 6 | pending |
| 103 | 1 | cancelled |
| product_id | name | price | stock |
|---|---|---|---|
| 1 | ワイヤレスマウス | 3500 | 80 |
| 6 | Webカメラ HD | 6500 | 15 |
IS NULL述語 × IN サブクエリ — NULL安全な絞り込みと動的リストフィルタ
SQLの三値論理(TRUE / FALSE / UNKNOWN)では、NULL との比較はすべて UNKNOWN を返します。WHERE 句では UNKNOWN は FALSE と同様に行が除外されます。NULL かどうかを調べるには IS NULL / IS NOT NULL の使用が必須です。
-- ✗ = NULL は常に UNKNOWN(絶対に使わない) WHERE shipped_at = NULL -- 常に UNKNOWN → 全行除外(意図と真逆) WHERE shipped_at <> NULL -- 常に UNKNOWN → 全行除外 -- ✓ IS NULL / IS NOT NULL が唯一正しい書き方 WHERE shipped_at IS NULL -- NULL の行のみ残す(未発送検出) WHERE shipped_at IS NOT NULL -- NULL 以外の行のみ(発送済み) -- NULL は演算に伝播する SELECT amount + discount_amount -- discount が NULL → 結果も NULL SELECT COALESCE(discount_amount, 0) -- COALESCE で NULL を代替値に変換
NULL = NULL の評価結果は TRUE ではなく UNKNOWN です。「NULL 同士は等しい」とは見なされません。PostgreSQL では NULL IS NOT DISTINCT FROM NULL で NULL 同士を等しいとして比較できます。orders テーブルから、status が 'processing' かつ shipped_at が NULL(未発送)の注文のうち、plan が 'premium' のユーザーの注文を IN サブクエリで取得してください。取得列は order_id, user_id, amount, status、amount 降順で返してください。
| order_id | user_id | amount | status | shipped_at |
|---|---|---|---|---|
| 101 | 1 | 8000 | completed | 2024-01-15 |
| 102 | 2 | 5000 | processing | NULL |
| 103 | 1 | 12000 | processing | NULL |
| 104 | 3 | 3000 | completed | 2024-01-20 |
| 105 | 4 | 9500 | processing | 2024-01-25 |
| 106 | 3 | 7000 | processing | NULL |
| user_id | name | plan |
|---|---|---|
| 1 | 田中 太郎 | premium |
| 2 | 佐藤 花子 | premium |
| 3 | 鈴木 一郎 | standard |
| 4 | 山田 次郎 | free |
| order_id | user_id | amount | status |
|---|---|---|---|
| 103 | 1 | 12000 | processing |
| 102 | 2 | 5000 | processing |
ANY述語 / ALL述語 — 複数値との部分比較・全件比較(競合価格分析)
ANY述語(SOME とも書ける)は「サブクエリの値のうちいずれか1つとの比較が TRUE なら TRUE」を返します。ALL述語は「サブクエリの値すべてとの比較が TRUE なら TRUE」を返します。
-- ANY: いずれか1つとの比較がTRUE → TRUE(OR の連鎖に相当) WHERE price < ANY (SELECT price FROM competitor) -- ↑ 等価: WHERE price < (SELECT MAX(price) FROM competitor) -- ALL: すべての値との比較がTRUE → TRUE(AND の連鎖に相当) WHERE price > ALL (SELECT price FROM competitor) -- ↑ 等価: WHERE price > (SELECT MAX(price) FROM competitor) -- 特殊な等価変換 WHERE col = ANY (list) -- = IN (list) と等価 WHERE col <> ALL (list) -- = NOT IN (list) と等価(NULLの罠あり)
val <> NULL が UNKNOWN になり ALL 全体が FALSE になります。<> ALL(= NOT IN 相当)は NULL 混入で全行除外される問題があるため、実務では NOT EXISTS を優先しましょう。products(自社)テーブルから、同一カテゴリの競合製品(competitor_prices テーブル)の価格のいずれかより安い商品を < ANY で取得してください。取得列は product_id, name, price、price 昇順。(解説タブで ALL バリアントも確認してください)
| product_id | name | category | price |
|---|---|---|---|
| 1 | モデルA ヘッドフォン | オーディオ | 12000 |
| 2 | モデルB ヘッドフォン | オーディオ | 8500 |
| 3 | モデルC ヘッドフォン | オーディオ | 15000 |
| 4 | モデルD ヘッドフォン | オーディオ | 6000 |
| comp_id | name | category | price |
|---|---|---|---|
| 1 | 競合X ヘッドフォン | オーディオ | 9000 |
| 2 | 競合Y ヘッドフォン | オーディオ | 11000 |
| 3 | 競合Z ヘッドフォン | オーディオ | 13500 |
| product_id | name | price |
|---|---|---|
| 4 | モデルD ヘッドフォン | 6000 |
| 2 | モデルB ヘッドフォン | 8500 |
| 1 | モデルA ヘッドフォン | 12000 |
複数EXISTS の応用総合 — AND EXISTS + NOT EXISTS による精密な行選択
複数の EXISTS / NOT EXISTS を AND で連結することで「条件Aを満たす行が存在し、かつ条件Bを満たす行は存在しない」という精密な行選択が実現できます。これは実務の行動分析・セグメント抽出で最も頻出するパターンのひとつです。
-- 複数 EXISTS を AND で連結 WHERE EXISTS ( -- ① 条件A を満たす行が存在する SELECT 1 FROM orders o1 WHERE o1.user_id = u.user_id AND o1.status = 'completed' ) AND NOT EXISTS ( -- ② 条件B を満たす行は存在しない SELECT 1 FROM orders o2 WHERE o2.user_id = u.user_id AND o2.status = 'cancelled' )
users テーブルから、plan が 'premium' または 'standard' のユーザーのうち、status が 'completed' の注文が1件以上あり、かつ status が 'cancelled' の注文が1件もないユーザーを取得してください。取得列は user_id, name, plan、user_id 昇順で返してください。
| user_id | name | plan |
|---|---|---|
| 1 | 田中 太郎 | premium |
| 2 | 佐藤 花子 | standard |
| 3 | 鈴木 一郎 | premium |
| 4 | 山田 次郎 | free |
| 5 | 伊藤 三郎 | standard |
| order_id | user_id | status |
|---|---|---|
| 101 | 1 | completed |
| 102 | 1 | cancelled |
| 103 | 2 | completed |
| 104 | 3 | completed |
| 105 | 3 | pending |
| 106 | 5 | pending |
| user_id | name | plan |
|---|---|---|
| 2 | 佐藤 花子 | standard |
| 3 | 鈴木 一郎 | premium |