「複数のキーワードのどれかに一致する行を取得したい」「IN 句のように LIKE でも複数の値を一括指定したい」という場面は実務でよく起こります。残念ながら SQL に IN LIKE という構文は存在しません。
この記事では OR + LIKE の基本から、パターン数が増えた時の REGEXP・UNION ALL・CASE WHEN による効率的な書き方、そしてアプリケーションから動的に複数キーワードを渡す実装パターンまで体系的に解説します。
-- products テーブル(商品) -- id | name | category | price | description -- 1 | りんごジュース | 飲料 | 180 | 国産りんご100% -- 2 | みかんゼリー | 菓子 | 120 | 愛媛産みかん使用 -- 3 | 有機ほうれん草 | 野菜 | 350 | 有機JAS認定 -- 4 | オレンジジュース(輸入) | 飲料 | 150 | ブラジル産オレンジ -- 5 | いちごアイス | 菓子 | 280 | 北海道産いちご -- 6 | 有機トマト | 野菜 | 400 | 有機JAS認定 -- 7 | レモンサワー | 飲料 | 220 | 国産レモン使用 -- 8 | グレープフルーツ果汁 | 飲料 | 200 | 輸入グレープフルーツ -- customers テーブル(顧客) -- id | name | email | pref -- 1 | 田中 太郎 | tanaka@example.com | 東京都 -- 2 | 鈴木 花子 | suzuki@gmail.com | 大阪府 -- 3 | 高橋 一郎 | takahashi@yahoo.co.jp | 愛知県 -- 4 | 伊藤 次郎 | ito@example.com | 東京都 -- 5 | 渡辺 美咲 | watanabe@company.co.jp | 神奈川県
基本:OR で複数の LIKE を並べる
SQL で複数パターンの LIKE を指定する最もシンプルな方法は、OR で LIKE を連結することです。LIKE のワイルドカード(%・_)の詳細はLIKE 演算子完全ガイドを参照してください。
-- 「りんご」または「みかん」を名前に含む商品 SELECT id, name, category, price FROM products WHERE name LIKE '%りんご%' OR name LIKE '%みかん%'; -- 結果: -- id=1: りんごジュース ✓ -- id=2: みかんゼリー ✓ -- 3パターン: 「ジュース」または「ゼリー」または「アイス」 SELECT id, name, price FROM products WHERE name LIKE '%ジュース%' OR name LIKE '%ゼリー%' OR name LIKE '%アイス%'; -- 前方一致の複数パターン(インデックスが効く) SELECT id, name FROM products WHERE name LIKE 'りんご%' OR name LIKE 'みかん%' OR name LIKE '有機%';
- AND より OR の優先度が低いため、AND と混在する場合は括弧で囲む:
(name LIKE '%A%' OR name LIKE '%B%') AND price < 300 - 前方一致(
'キーワード%')はインデックスが効くが、中間一致('%キーワード%')はフルスキャンになる - パターンが 5 個以上になる場合は後述の REGEXP や UNION ALL を検討する
「IN + LIKE」はできない — よくある誤解
IN は完全一致の複数値指定です。LIKE のワイルドカード(%)は IN の中では機能しません。この誤解から書いてしまいがちな間違い構文を確認しておきましょう。
-- NG: IN の中に LIKE パターンを書いてもワイルドカードは効かない
SELECT * FROM products
WHERE name IN ('%ジュース%', '%ゼリー%');
-- → '%ジュース%' という文字列と完全一致を探すため 0 件になる(バグ)
-- NG: LIKE IN という構文は存在しない(構文エラー)
-- WHERE name LIKE IN ('%ジュース%', '%ゼリー%'); -- エラー
-- OK: OR で LIKE を並べる(確実に動作する)
SELECT * FROM products
WHERE name LIKE '%ジュース%'
OR name LIKE '%ゼリー%';
-- OK: 完全一致なら IN を使う(ワイルドカード不要なケース)
SELECT * FROM products
WHERE category IN ('飲料', '菓子');
IN ('%A%', '%B%') は ‘%A%’ や ‘%B%’ という文字列そのものとの完全一致を探します。LIKE のワイルドカードが有効なのは LIKE 演算子と組み合わせた場合のみです。完全一致の複数値を検索する場合は IN、パターン一致の複数値は OR + LIKE を使ってください。REGEXP / SIMILAR TO で複数パターンを 1 行に書く
パターン数が多くなると OR + LIKE では行数が増えて読みにくくなります。正規表現(REGEXP / SIMILAR TO / ~)を使うと、複数パターンを |(OR)で 1 行にまとめられます。
| RDBMS | 複数パターン検索の構文 | 大文字小文字 |
|---|---|---|
| MySQL | REGEXP 'パターン1|パターン2' |
照合順序依存(デフォルト: 区別しない) |
| PostgreSQL | ~ 'パターン1|パターン2' または SIMILAR TO '%(A|B)%' |
~ は区別する、~* は区別しない |
| SQL Server | LIKE '[パターン]' または CHARINDEX を OR で並べる |
照合順序依存 |
| Oracle | REGEXP_LIKE(列, 'パターン1|パターン2') |
第3引数 'i' で区別しない |
-- MySQL: REGEXP で「ジュース」または「ゼリー」または「アイス」を含む SELECT id, name, price FROM products WHERE name REGEXP 'ジュース|ゼリー|アイス'; -- 結果: id=1,2,4,5,7 がマッチ -- 前方一致の複数パターン(^ で行頭を指定) SELECT id, name FROM products WHERE name REGEXP '^(りんご|みかん|有機)'; -- 大文字小文字を区別する場合 SELECT id, name FROM products WHERE name REGEXP BINARY 'Juice|juice'; -- NOT REGEXP で除外 SELECT id, name FROM products WHERE name NOT REGEXP 'ジュース|アイス';
-- PostgreSQL: ~ で「ジュース」または「ゼリー」を含む SELECT id, name, price FROM products WHERE name ~ 'ジュース|ゼリー'; -- 大文字小文字を区別しない: ~* を使う SELECT id, name FROM products WHERE email ~* '@example|@gmail'; -- SIMILAR TO: SQL 標準に近い正規表現 -- % はワイルドカード(LIKE の % と同じ) SELECT id, name FROM products WHERE name SIMILAR TO '%(ジュース|ゼリー|アイス)%'; -- NOT SIMILAR TO で除外 SELECT id, name FROM products WHERE name NOT SIMILAR TO '%(飲料|菓子)%';
-- Oracle: REGEXP_LIKE で複数パターンを検索
SELECT id, name, price
FROM products
WHERE REGEXP_LIKE(name, 'ジュース|ゼリー|アイス');
-- 大文字小文字を区別しない(第3引数 'i')
SELECT id, name
FROM customers
WHERE REGEXP_LIKE(email, '@example|@gmail', 'i');
-- SQL Server: LIKE を OR で並べるか CHARINDEX を使う
-- CHARINDEX(検索文字列, 対象列) > 0 でパターンに含まれるか判定
SELECT id, name
FROM products
WHERE CHARINDEX('ジュース', name) > 0
OR CHARINDEX('ゼリー', name) > 0;
REGEXP・REGEXP_LIKE・SIMILAR TO の正規表現パターン詳細(文字クラス・繰り返し・アンカー等)は正規表現で特定のパターン以外を抽出する方法で解説しています。
UNION ALL でインデックスを最大限活かす
中間一致('%キーワード%')は通常インデックスが使われませんが、前方一致('キーワード%')はインデックスが有効です。前方一致の複数パターンを OR ではなく UNION ALL で分割することで、各クエリがインデックスを活用できます。
-- 方法1: OR + LIKE(オプティマイザがインデックスを使わないことがある) SELECT id, name FROM products WHERE name LIKE 'りんご%' OR name LIKE 'みかん%' OR name LIKE '有機%'; -- 方法2: UNION ALL(各クエリが確実にインデックスを使う) SELECT id, name FROM products WHERE name LIKE 'りんご%' UNION ALL SELECT id, name FROM products WHERE name LIKE 'みかん%' UNION ALL SELECT id, name FROM products WHERE name LIKE '有機%'; -- 重複を除きたい場合は UNION(DISTINCT)を使う SELECT id, name FROM products WHERE name LIKE 'りんご%' UNION SELECT id, name FROM products WHERE name LIKE 'みかん%'; -- UNION ALL の方が重複除去のコストがなく高速(重複がないと分かっている場合)
- 前方一致パターンが複数ある場合(インデックスを確実に活用)
- 各パターンの件数が少なく、重複がほとんどない場合
- OR 条件でオプティマイザがフルスキャンを選んでしまうとき(EXPLAIN で確認)
- 中間一致でも「UNION で分割 + 各結果を絞り込む」戦略が効く場合がある
CASE WHEN + LIKE でどのパターンにマッチしたか判定する
「マッチした商品を取得するだけでなく、どのキーワードにマッチしたかを知りたい」場合、CASE WHEN ... LIKE ... THEN でパターンを分類できます。マーケティング分析・ログ分類・タグ付けなどで活用できます。
-- どのカテゴリキーワードにマッチするか判定
SELECT
id,
name,
price,
CASE
WHEN name LIKE '%ジュース%' THEN 'ジュース系'
WHEN name LIKE '%ゼリー%' THEN 'ゼリー系'
WHEN name LIKE '%アイス%' THEN 'アイス系'
WHEN name LIKE '有機%' THEN '有機系'
ELSE 'その他'
END AS キーワード分類
FROM products
ORDER BY キーワード分類, price;
-- 複数キーワードにマッチするか全てフラグで出す
SELECT
id,
name,
CASE WHEN name LIKE '%ジュース%' THEN 1 ELSE 0 END AS is_juice,
CASE WHEN name LIKE '%有機%' THEN 1 ELSE 0 END AS is_organic,
CASE WHEN name LIKE '%輸入%' THEN 1 ELSE 0 END AS is_imported
FROM products;
-- 結果:
-- id=1: りんごジュース is_juice=1, is_organic=0, is_imported=0
-- id=3: 有機ほうれん草 is_juice=0, is_organic=1, is_imported=0
-- id=4: オレンジジュース is_juice=1, is_organic=0, is_imported=1
-- キーワード分類ごとの件数と平均価格
SELECT
CASE
WHEN name LIKE '%ジュース%' OR name LIKE '%果汁%' THEN '飲料系'
WHEN name LIKE '%ゼリー%' OR name LIKE '%アイス%' THEN 'スイーツ系'
WHEN name LIKE '有機%' THEN '有機系'
ELSE 'その他'
END AS 分類,
COUNT(*) AS 件数,
ROUND(AVG(price)) AS 平均価格,
MIN(price) AS 最安値,
MAX(price) AS 最高値
FROM products
GROUP BY
CASE
WHEN name LIKE '%ジュース%' OR name LIKE '%果汁%' THEN '飲料系'
WHEN name LIKE '%ゼリー%' OR name LIKE '%アイス%' THEN 'スイーツ系'
WHEN name LIKE '有機%' THEN '有機系'
ELSE 'その他'
END
ORDER BY 件数 DESC;
パターン数・データ量別の選択指針
| パターン数 / データ量 | 推奨手法 | 理由 |
|---|---|---|
| 2〜4 パターン、小〜中規模 | OR + LIKE |
シンプルで読みやすい。小規模ならパフォーマンスも問題なし |
| 5 パターン以上、中規模 | REGEXP / SIMILAR TO |
1 行で書けて可読性が上がる(ただし SQL 標準外) |
| 前方一致の複数パターン | UNION ALL で分割 |
各クエリがインデックスを確実に使える |
| 大量パターン(数十以上) | アプリ側で動的生成 / 全文検索 | SQL 文が巨大になりすぎる。管理コストが高い |
| 数十万件以上の中間一致 | FULLTEXT / FTS / 外部検索エンジン | LIKE の中間一致はフルスキャンで遅すぎる |
アプリからの動的複数キーワード検索
検索フォームでユーザーが複数のキーワードを入力する場合、アプリ側で SQL を動的に構築するか、全文検索エンジンに移行することを検討します。
-- 例: ユーザーが「りんご みかん 有機」と入力(スペース区切り)した場合
-- アプリ側でキーワードを分割して SQL を構築する
-- Python の例(擬似コード)
-- keywords = ["りんご", "みかん", "有機"]
-- conditions = " OR ".join([f"name LIKE '%{kw}%'" for kw in keywords])
-- sql = f"SELECT * FROM products WHERE {conditions}"
-- → WHERE name LIKE '%りんご%' OR name LIKE '%みかん%' OR name LIKE '%有機%'
-- 注意: 上記は SQL インジェクション対策が必要!
-- 必ずプレースホルダー(パラメータバインド)を使う
-- Python pymysql での安全な実装
-- keywords = ["りんご", "みかん", "有機"]
-- conditions = " OR ".join(["name LIKE %s"] * len(keywords))
-- params = [f"%{kw}%" for kw in keywords]
-- sql = f"SELECT * FROM products WHERE {conditions}"
-- cursor.execute(sql, params)
ユーザー入力をそのまま SQL 文字列に埋め込むと SQL インジェクション脆弱性が生じます。必ずプレースホルダー(
%s・?・:param)とパラメータバインドを使ってください。LIKE のパターンでも %s で安全に渡せます(アプリ側で %keyword% という文字列を作ってバインドする)。-- FULLTEXT INDEX を使ったキーワード検索(中間一致の代替として高速)
-- まず FULLTEXT INDEX を作成
ALTER TABLE products ADD FULLTEXT INDEX ft_name_desc (name, description);
-- BOOLEAN MODE で複数キーワードの AND 検索
-- +キーワード: 必須(AND 相当)
SELECT id, name, price
FROM products
WHERE MATCH(name, description) AGAINST ('+りんご +国産' IN BOOLEAN MODE);
-- いずれかのキーワードを含む(OR 相当、+ なし)
SELECT id, name, price
FROM products
WHERE MATCH(name, description) AGAINST ('りんご みかん 有機' IN BOOLEAN MODE);
-- ↑ 「りんご」OR「みかん」OR「有機」を含む行を関連度順で返す
-- 除外キーワード(- を前置)
SELECT id, name, price
FROM products
WHERE MATCH(name, description) AGAINST ('ジュース -輸入' IN BOOLEAN MODE);
-- ↑ 「ジュース」を含み「輸入」を含まない
-- PostgreSQL の全文検索
-- to_tsquery で OR 検索(|)
SELECT id, name
FROM products
WHERE to_tsvector('japanese', name) @@ to_tsquery('japanese', 'りんご | みかん | 有機');
-- pg_trgm 拡張で LIKE の中間一致を高速化(GIN インデックス)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_products_name_trgm ON products USING GIN (name gin_trgm_ops);
-- インデックスを使った中間一致(複数 OR も高速)
SELECT id, name FROM products
WHERE name LIKE '%ジュース%'
OR name LIKE '%ゼリー%';
-- GIN インデックスがあれば %キーワード% でも高速
NOT LIKE で複数パターンを除外する
複数パターンを除外する場合は AND NOT LIKE(全条件を除外)かNOT REGEXP(正規表現でまとめて除外)を使います。
-- 「ジュース」と「アイス」の両方を含まない商品 -- AND NOT LIKE: 両方を除外(ジュースでも アイスでもない) SELECT id, name FROM products WHERE name NOT LIKE '%ジュース%' AND name NOT LIKE '%アイス%'; -- 「ジュース」または「アイス」を含まない(少なくとも片方を含まない) -- → 上記と違い、「ジュースアイス」はどちらの条件も False になるため残らない -- 実務では AND NOT LIKE の方が直感的 -- REGEXP で一括除外(MySQL) SELECT id, name FROM products WHERE name NOT REGEXP 'ジュース|アイス'; -- Oracle で一括除外 SELECT id, name FROM products WHERE NOT REGEXP_LIKE(name, 'ジュース|アイス'); -- SQL Server で一括除外 SELECT id, name FROM products WHERE name NOT LIKE '%ジュース%' AND name NOT LIKE '%アイス%'; -- SQL Server は REGEXP がないため AND NOT LIKE を並べる
よくある質問(FAQ)
WHERE name IN ('%A%', '%B%') と書いたら 0 件になりました。'%A%' という文字列そのものとの完全一致を探すため、通常のデータには 0 件になります。パターン一致で複数の値を検索するには OR で LIKE を並べてください:WHERE name LIKE '%A%' OR name LIKE '%B%'。'パターン1|パターン2|...' の形で 1 行にまとめられます。さらにパターンが動的(ユーザー入力)であれば、アプリ側でプレースホルダーを使って動的に SQL を生成するか、全文検索(FULLTEXT / FTS)への移行を検討してください。OR + LIKE と REGEXP のどちらが速いですか?%キーワード%)ではどちらもインデックスが使われないためほぼ同等(フルスキャン)です。前方一致(キーワード%)では OR + LIKE の各条件がインデックスを使える場合があり、UNION ALL で分割するとより確実にインデックスが効きます。大量データでの高速化には FULLTEXT INDEX(MySQL)や GIN インデックス(PostgreSQL)を使ってください。WHERE col LIKE '%A%' AND col LIKE '%B%'、「A または B を含む(OR)」は WHERE col LIKE '%A%' OR col LIKE '%B%' です。正規表現では AND に相当するものが標準にないため、AND の場合は LIKE を AND で並べる方が明快です。MySQL の FULLTEXT BOOLEAN MODE では '+A +B' が AND 相当、'A B' が OR 相当です。OR 列 IS NULL を明示的に追加してください:WHERE (name LIKE '%A%' OR name LIKE '%B%') OR name IS NULL。まとめ
| やりたいこと | 書き方 |
|---|---|
| 2〜4 パターンのいずれかに一致 | WHERE col LIKE '%A%' OR col LIKE '%B%' |
| IN のように複数パターンを指定(誤解) | IN はワイルドカード不可。OR + LIKE を使う |
| 5 パターン以上を 1 行で書く | MySQL: REGEXP 'A|B|C' / PostgreSQL: ~ 'A|B|C' / Oracle: REGEXP_LIKE(col, 'A|B|C') |
| 前方一致複数パターンでインデックスを活かす | UNION ALL で各パターンに分割 |
| マッチしたパターンで分類・集計 | CASE WHEN col LIKE '%A%' THEN 'A系' ... |
| 複数パターンを除外 | NOT LIKE '%A%' AND NOT LIKE '%B%' |
| 大量データの複数キーワード検索 | MySQL: FULLTEXT BOOLEAN MODE / PostgreSQL: GIN + pg_trgm |
| アプリからの動的キーワード検索 | プレースホルダーで安全にパラメータバインド(SQL インジェクション対策) |
LIKE 演算子のワイルドカード・エスケープ・大文字小文字・パフォーマンスの詳細はLIKE 演算子完全ガイドを参照してください。WHERE 句で複数条件を組み合わせる方法も合わせてご覧ください。

