| この記事の要点 |
|
WHERE 句とは
WHERE 句は SQL の SELECT / UPDATE / DELETE 文で対象行を絞り込むための条件指定です。条件は各行に対して評価され、true となった行だけが結果に含まれます。
基本構文
Oracle Database 19cのWHERE句を軸に、他製品の例を区別して説明します。以下の日本語は構文図、users/logs等は説明用の表です。UPDATE/DELETEはデータを変更する例で、実業務の表へそのまま実行する手順ではありません。日時列を文字列と比較する例は型変換に依存するため、OracleのDATE列では、例えば last_login < DATE '2023-01-01' のように型付き境界を使います。
SELECT カラム FROM テーブル WHERE 条件;
UPDATE テーブル SET カラム = 値 WHERE 条件;
DELETE FROM テーブル WHERE 条件;
-- 例
SELECT * FROM users WHERE id = 1;
SELECT id, name FROM users WHERE age >= 20 AND status = 'active';
UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01';
DELETE FROM logs WHERE created_at < '2024-01-01';
test_tableの入力と抽出結果
文字列列COL1/COL2だけを持つ説明用test_tableに、次の2行だけがあると仮定します。
入力(test_table)
| COL1 | COL2 |
|---|---|
| AAA | ZZZ |
| BBB | XXX |
SELECT * FROM test_table WHERE col1 = 'AAA';
出力(条件から導く期待値)
| COL1 | COL2 |
|---|---|
| AAA | ZZZ |
COL1がAAAの行だけを選ぶため、AAA/ZZZが残り、BBB/XXXは除かれます。これは上の入力からの期待値で、DBで実測した出力ではありません。
比較演算子
| 演算子 | 意味 | 例 |
|---|---|---|
= | 等しい | id = 1 |
<> / != | 等しくない | status <> 'deleted' |
< | 未満 | age < 18 |
> | より大きい | score > 80 |
<= | 以下 | price <= 1000 |
>= | 以上 | created_at >= DATE '2024-01-01'(OracleのDATE列の例) |
論理演算子 (AND / OR / NOT)
-- AND: 両方真
SELECT * FROM users WHERE age >= 20 AND status = 'active';
-- OR: いずれか真
SELECT * FROM users WHERE role = 'admin' OR role = 'editor';
-- NOT: 否定
SELECT * FROM users WHERE NOT (status = 'deleted');
-- 優先順位: NOT > AND > OR → 括弧で明示推奨
SELECT * FROM products
WHERE (category = 'food' OR category = 'drink')
AND price < 1000;
-- 括弧無しだと意図と異なる
WHERE category = 'food' OR category = 'drink' AND price < 1000
-- ⇔ WHERE category = 'food' OR (category = 'drink' AND price < 1000)
LIKE (パターンマッチ)
| ワイルドカード | 意味 |
|---|---|
% | 0 文字以上の任意の文字列 |
_ | 任意の 1 文字 |
下の索引コメントは断定し過ぎです。先頭固定のLIKEは通常B-treeで範囲探索の候補ですが、採用保証ではありません。先頭%/_はその探索に向かず、LOWERには対応する関数索引等の可能性もあります。Oracleでは掲載のバックスラッシュ1文字のESCAPE例は正当です。MySQLはSQL文字列のバックスラッシュ解釈が異なるため、そのまま共通例にせず、比較には下の既存 ESCAPE '!' 例を使えます。
-- 前方一致 (インデックス効く)
WHERE name LIKE 'Yam%' -- Yamada, Yamaguchi, Yamamoto
-- 後方一致 (インデックス効かない)
WHERE name LIKE '%hi'
-- 部分一致 (インデックス効かない)
WHERE name LIKE '%am%'
-- 任意 1 文字
WHERE code LIKE 'A_01' -- AB01, AC01, A001 等
-- エスケープ
WHERE name LIKE '50\%引%' ESCAPE '\' -- "50%引" で始まる
WHERE name LIKE '50!%引%' ESCAPE '!' -- 同上 (ESCAPE 文字を ! に)
-- 大文字小文字
WHERE name LIKE 'yam%' -- MySQL/MariaDB: utf8_general_ci なら大小区別なし
WHERE LOWER(name) LIKE 'yam%' -- 明示的に小文字化 (インデックス効かない)
IN / NOT IN
下の単列条件 id NOT IN (1, 2, NULL) は、idが1/2ならFALSE、それ以外やNULLならUNKNOWNで、TRUEの行はありません。NULLを除く方法とNOT EXISTSへの書換えは、外側のNULLを残すかも含めて選びます。
-- リストに含まれる
WHERE status IN ('active', 'pending')
WHERE id IN (1, 2, 3, 4, 5)
-- リストに含まれない
WHERE status NOT IN ('deleted', 'banned')
-- サブクエリ
WHERE user_id IN (
SELECT id FROM users WHERE status = 'active'
)
-- ★ NULL の罠
WHERE id NOT IN (1, 2, NULL)
-- → 常に何もマッチしない (NULL との比較は UNKNOWN)
-- 対処: NULL を含まないようにする or NOT EXISTS を使う
BETWEEN
BETWEENは指定した両端を含みます。下の年末0時という説明は文字列の日時変換が成功する前提です。OracleのDATE列で2024年全体を選ぶ訂正形は created_at >= DATE '2024-01-01' AND created_at < DATE '2025-01-01'。TIMESTAMP列では型を合わせた境界を使い、タイムゾーン付き列では対象とする年の基準地域も決めます。
-- 範囲指定 (両端含む)
WHERE price BETWEEN 1000 AND 5000
-- ⇔ WHERE price >= 1000 AND price <= 5000
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
-- 否定
WHERE price NOT BETWEEN 1000 AND 5000
-- ★ 日付の罠: BETWEEN は 23:59:59.999 を含まない
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31'
-- → 2024-12-31 00:00:00 までしか含まれない!
-- 正しくは:
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
IS NULL / IS NOT NULL
NULL は = NULL では判定できません。必ず IS NULL / IS NOT NULL:
下の「常に false」は誤りで、通常のNULL比較はUNKNOWNです。WHEREはTRUEだけを選びます。文字列emailについて、Oracle 19cは空文字をNULLと扱うため COALESCE(email, '') = '' はNULL行を選びません。Oracleでの訂正形は email IS NULL。COALESCE/IFNULLでNULLまたは空文字を選ぶ比較はMySQL向けです。一方 NVL(email, '') IS NULL はOracleの文字列emailではNULL判定になり、この行自体は誤りではありません。
-- ❌ 動かない (常に false)
WHERE email = NULL
WHERE email <> NULL
-- ✅ 正しい
WHERE email IS NULL
WHERE email IS NOT NULL
-- NULL を別の値として扱う
WHERE COALESCE(email, '') = '' -- NULL または空文字
WHERE IFNULL(email, '') = '' -- MySQL
WHERE NVL(email, '') IS NULL -- Oracle
EXISTS / NOT EXISTS
下の末尾にある「EXISTSは大規模データで速い」「INは全件展開」は固定的な説明として誤りです。OracleではIN/EXISTSの両方がsemijoinへの変換候補です。存在判定の意味とNULLの扱いを合わせ、計画と実測を別に比較します。
-- 注文を 1 件以上持つユーザー
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- 注文を持たないユーザー
SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- IN との違い: EXISTS は 1 件見つけた時点で終わる → 大規模データで速い
-- IN は全件展開してから判定 → 小規模リスト向き
CASE WHEN (条件分岐)
下の @flag はMySQLのユーザー変数を使う比較例で、Oracleではアプリから値を渡す CASE WHEN :flag = 1 THEN status END = 'active' のようなバインド例と区別します。ELSE省略時はNULLとなるためflagが1以外/NULLでもフィルタ無効にはならず、行が除外されます。また年齢例のageがNULLならELSEの「高齢者」に進むので、年齢不明を区別する用途では先に WHEN age IS NULL THEN '不明' が必要です。
-- SELECT で使うのが一般的だが WHERE でも使える
SELECT id,
CASE
WHEN age < 20 THEN '未成年'
WHEN age < 65 THEN '成人'
ELSE '高齢者'
END AS age_group
FROM users;
-- WHERE で動的フィルタ
WHERE
CASE WHEN @flag = 1 THEN status END = 'active'
SARGable: インデックスによる探索の候補
SARGable (Search ARGument able) は索引による探索を検討できる条件の形です。下の「関数でラップすると使えない」は通常の列索引による範囲探索の話で、関数/式索引等は別です。「SARGable」も採用や速度の保証ではありません。YEAR()/NOW()/INTERVAL 1 DAYはMySQL向けの比較例で、Oracle 19cの日時構文として転用しません。上下の条件は無条件に同値ではなく、email比較は照合規則、価格の変形は数値型・丸めを確認します。
-- ❌ Non-SARGable: 関数でラップ → インデックス使えない
WHERE YEAR(created_at) = 2024
WHERE UPPER(email) = 'FOO@BAR.COM'
WHERE created_at + INTERVAL 1 DAY > NOW()
WHERE name LIKE '%abc%' -- 先頭ワイルドカード
WHERE price * 1.1 > 1000
-- ✅ SARGable: カラムは素のまま、定数側で計算
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
WHERE email = LOWER('Foo@Bar.com') -- email は CI 照合順序前提
WHERE created_at > NOW() - INTERVAL 1 DAY
WHERE name LIKE 'abc%'
WHERE price > 1000 / 1.1
Prepared Statement / SQL Injection 対策
WHERE 句にユーザー入力を直接埋め込むと SQL Injection 脆弱性になります。必ずバインドパラメータを使ってください:
// ❌ 危険: SQL Injection
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
// /?id=1 OR 1=1 → 全件取得される
// ✅ 安全: PDO の Prepared Statement
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_GET['id']]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ✅ Laravel Query Builder
$users = DB::table('users')->where('id', $request->id)->get();
// ✅ 名前付きバインド
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
$stmt->execute([':email' => $email, ':status' => 'active']);
UPDATE / DELETE の WHERE 抜け事故防止
以下の全行UPDATE/DELETEは事故の説明で、実行推奨ではありません。事前SELECTは有用ですが、後の更新対象を固定しません。同時更新・対象ID/件数・トランザクションと取消可能な時点を別に確認します。末尾のMySQL sql_safe_updates は、キーを使うWHEREまたはLIMITのないUPDATE/DELETEを拒否する補助です。WHEREなしでもLIMIT付きなら許可され得て、意図した対象だけを変更する保証はありません。「本番推奨」「WHERE無しをエラーに」という一律の説明と設定断片を導入手順にせず、Oracleの機能とも混同しないでください。
-- ❌ WHERE 抜け = 全件更新・全件削除
UPDATE users SET status = 'inactive'; -- 全ユーザーが inactive に!
DELETE FROM users; -- 全ユーザーが消えた!
-- ✅ 事前に SELECT で対象確認
SELECT * FROM users WHERE last_login < '2023-01-01';
-- → 対象 OK と確認してから
UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01';
-- ✅ MySQL の safe mode (本番推奨)
-- my.cnf: sql_safe_updates = 1
-- → WHERE 無しの UPDATE/DELETE をエラーに
FAQ
Q: AND と OR、どっちが先に評価される?
A: AND が先(優先順位が高い)。意図通りにするため括弧で明示するのが安全です。
Q: WHERE 1=1 をよく見るのはなぜ?
A: 動的 SQL 構築時、後続の AND ... を機械的に追加できるテクニックです。例: SELECT * FROM t WHERE 1=1 に条件を増やしていく。
Q: NOT IN と NOT EXISTS どちらを使うべき?
A: 単列の NOT IN (..., NULL) はTRUEにならない点に注意します。NOT EXISTS が常に同値とは限りません。等号で相関するNOT EXISTSは外側のキーがNULLでも行を残し得るので、外側NULLを含めたいかを先に決めます。
公式仕様の確認先:Oracle19c NULL、MySQL8.4 sql_safe_updates(2026-10-28確認)。画面・設定・コードは対象版と各例の前提条件を照合してください。
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09