記事の目的
SQL の LIKE 句を使って、文字列を「ぴったり一致」ではなく「あいまい」に検索できるようになります。
ワイルドカード % と _ の意味、前方一致・後方一致・中間一致の書き方、% や _ そのものを検索したいときのエスケープ、否定形の NOT LIKE 、そして 3 製品で挙動が大きく違う「大文字・小文字の区別」までを、PostgreSQL・MySQL・SQL Server の製品差に注意しながら整理します。
記事のサンプルテーブルの前提
本記事では、説明用に次のような products テーブルがある前提で進めます。
- id :自動採番(商品 ID)
- product_name :商品名(文字列)
- category :カテゴリ
- price :価格
実際の CREATE TABLE 文は別記事で扱います。本文中では次のサンプルデータがすでに入っているものとして説明します。
SELECT * FROM products;
id | product_name | category | price
---+-------------------+----------+------
1 | 林檎ジュース | 飲料 | 200
2 | オレンジジュース | 飲料 | 250
3 | 緑茶 | 飲料 | 150
4 | 焼き林檎 | 食品 | 300
5 | 100%林檎ジュース | 飲料 | 400
6 | 100円チョコ | 食品 | 100
サンプルは6行です。100%林檎ジュース(id=5)と 100円チョコ(id=6)はどちらも「100」で始まるため、後段の「エスケープ」セクションで % を文字として扱う必要性を実例で確認します。
最短の答え
products から「商品名に “ジュース” を含む商品」を検索します。
- 主要な RDBMS(PostgreSQL / MySQL / SQL Server)で共通して使える基本形
SELECT id, product_name
FROM products
WHERE product_name LIKE '%ジュース%';実行結果は次のようになります。
id | product_name
---+-------------------
1 | 林檎ジュース
2 | オレンジジュース
5 | 100%林檎ジュース
- 「林檎」で始まる商品を検索(前方一致)
SELECT id, product_name FROM products
WHERE product_name LIKE '林檎%';- 「ジュース」で終わる商品を検索(後方一致)
SELECT id, product_name FROM products
WHERE product_name LIKE '%ジュース';ここから先は、% と _ の意味、エスケープ、製品差を順に解説します。
LIKE 構文の基本
LIKE は WHERE 句の中で、列の値を「パターン」と比較する演算子です。
SELECT 列 FROM テーブル
WHERE 文字列の列 LIKE 'パターン';LIKEの右側には文字列パターンを書きます。- パターンの中で、
%と_の 2 つの記号が特別な意味を持ちます(ワイルドカード)。 - パターンが列の値と一致した行だけが結果に含まれます。
- 元のテーブルは変わりません。あくまで検索条件です。
ワイルドカードを使わずに WHERE product_name LIKE '緑茶' と書いた場合は、= 演算子と同じく「完全一致」になります。LIKE の真価はワイルドカードを使うときに現れます。
ワイルドカード ── [ % ] と [ _ ]
主要な RDBMS(PostgreSQL / MySQL / SQL Server)で共通して覚えるべきワイルドカードは、まず % と _ の 2 つです。
| 記号 | 意味 |
|---|---|
% | 0文字以上の任意の文字列(何でも・空でも OK) |
_ | ちょうど1文字の任意の文字(必ず1文字。0文字や2文字は不可) |
% と _ の違いは、初心者が最初につまずくところです。% は「あってもなくてもいい」、_ は「必ず1文字必要」と覚えます。
なお、SQL Server には [abc] (候補のうちいずれか1文字)や [^d] (指定文字以外)といった角括弧によるパターン指定もあります。3製品で共通ではないため、本記事では % と _ を中心に扱い、SQL Server 固有の角括弧パターンは「エスケープ」のセクションで補足します。
-- 例1: % は 0 文字以上の任意の文字列
SELECT product_name FROM products WHERE product_name LIKE '林檎%';-- → 林檎ジュース、焼き林檎 は…?
-- 「林檎」で始まる商品なので、「林檎ジュース」だけがヒットします。
-- 「焼き林檎」は先頭が「焼」なのでヒットしません。
-- 例2: _ は必ず 1 文字
SELECT product_name FROM products WHERE product_name LIKE '_ジュース';-- → 何もヒットしません。
-- 「林檎ジュース」は「ジュース」の前に 2 文字(林・檎)あるためです。
前方一致・後方一致・中間一致
% をどこに置くかで、検索の意味が変わります。これは LIKE で最もよく使う使い分けです。
| パターン | 意味 | 用例 |
|---|---|---|
'林檎%' | 前方一致:「林檎」で始まる | 商品名が「林檎」で始まるもの |
'%ジュース' | 後方一致:「ジュース」で終わる | 商品名が「ジュース」で終わるもの |
'%林檎%' | 中間一致:「林檎」を含む | 商品名のどこかに「林檎」を含むもの |
-- 前方一致:「林檎」で始まる
SELECT id, product_name FROM products
WHERE product_name LIKE '林檎%';id | product_name
---+--------------
1 | 林檎ジュース
-- 中間一致:「林檎」を含む
SELECT id, product_name FROM products
WHERE product_name LIKE '%林檎%';id | product_name
---+-------------------
1 | 林檎ジュース
4 | 焼き林檎
5 | 100%林檎ジュース
「だいたいこれを含むものを取りたい」というユーザー検索の意図には、中間一致( '%…%' )が最もよく合います。ただし、後述する「インデックスの罠」があるため、件数が大きいテーブルでは使いすぎに注意します。
エスケープ ── [ % ] や [ _ ] そのものを検索したいとき
商品名に % や _ を含む場合、そのまま書くとワイルドカードと解釈されてしまいます。
-- これは「100 で始まるすべての商品名」と解釈される
SELECT product_name FROM products WHERE product_name LIKE '100%';実行すると、100%林檎ジュース(id=5)だけでなく、100円チョコ(id=6)も両方ヒットします。
id | product_name
---+-------------------
5 | 100%林檎ジュース
6 | 100円チョコ
「商品名がパーセント記号 % を含むものだけ」を取りたい場合は、エスケープ文字を使って「これはワイルドカードではない」と明示します。
-- 「100%」(文字としての % を含む)で始まる商品名(3 製品共通の書き方)
SELECT id, product_name FROM products
WHERE product_name LIKE '100!%%' ESCAPE '!';!%:エスケープ文字!で%を打ち消し、「文字としての %」の意味になります。- 末尾の
%:エスケープされていないので「以降に何があってもよい」というワイルドカードのまま。 ESCAPE '!':エスケープ文字を!に指定しています。
id | product_name
---+-------------------
5 | 100%林檎ジュース
100円チョコ は % を含まないので除外されました。ESCAPE の文字は ! でなくてもよく、パターンに登場しない文字を1つ選んで指定します(よく使われるのは ! や # などです)。
エスケープ文字のよくある選び方と製品差
3製品とも ESCAPE 'X' 構文に対応しており、上の書き方が共通で使えます。ただし、エスケープ文字を省略した場合の挙動は製品で異なります。
| 製品 | エスケープ文字を省略したとき |
|---|---|
| PostgreSQL | 既定で \ がエスケープ文字(ただし設定により変わるため ESCAPE 明示が安全) |
| MySQL | 既定で \ がエスケープ文字。文字列リテラル側でも \ がエスケープに使われるため、SQL モードや書き方で挙動が変わる |
| SQL Server | エスケープ文字に既定はなく、% をリテラルにしたい場合は ESCAPE を明示するか、後述の角括弧 [%] を使う |
3製品で同じ動きをさせたい場合は、ESCAPE '!' のように使わない記号を毎回明示するのが最も安全です。
SQL Server 固有:角括弧でリテラルを表す
SQL Server では [ ] を使う独自のパターン指定があり、これを % のエスケープ目的で使うこともできます。
-- SQL Server のみ:[%] は「文字としての %」の意味
SELECT id, product_name FROM products
WHERE product_name LIKE '100[%]%';PostgreSQL や MySQL では [%] はそのまま3文字の文字列として扱われ、意図通りに動きません。3製品共通で書きたい場合は、上記の ESCAPE '!' 方式を使います。
NOT LIKE ── 含まないものを検索
「LIKE の逆」は NOT LIKE で書きます。
-- 「ジュース」を含まない商品
SELECT id, product_name FROM products
WHERE product_name NOT LIKE '%ジュース%';id | product_name
---+--------------
3 | 緑茶
4 | 焼き林檎
6 | 100円チョコ
NOT LIKE も LIKE と同じワイルドカード( % _ )が使えます。ただし NULL の扱いには注意が必要です(後述)。
大文字・小文字の扱い(製品差)
LIKE の挙動でいちばん製品差が大きいのが、大文字・小文字を区別するかです。「同じ SQL でも結果が違う」典型例なので、扱う製品ごとに必ず確認します。
| 製品 | 既定の挙動 | 区別したい/したくないとき |
|---|---|---|
| PostgreSQL | 区別する(大文字小文字は別の文字として扱う) | 区別しない検索は ILIKE を使う |
| MySQL | 既定は区別しない(照合順序 utf8mb4_0900_ai_ci 等が既定の場合) | 区別したいなら BINARY 演算子や _bin 照合順序を使う |
| SQL Server | 照合順序による。日本語環境の既定は区別しないことが多い | 区別したいなら COLLATE 〜_BIN2 を指定する |
つまり、同じ WHERE product_name LIKE 'apple%' を書いても、PostgreSQL では Apple がヒットしないのに、MySQL では Apple も apple もヒットする、ということが普通に起きます。
PostgreSQL で大文字小文字を無視したい場合は ILIKE という独自演算子があります。詳しい使い分けや pg_trgm を使った高速化は別記事「PostgreSQL ILIKE と大文字小文字を無視したあいまい検索」で扱います。
-- PostgreSQL の例:ILIKE は大文字小文字を区別しない
SELECT product_name FROM products
WHERE product_name ILIKE 'apple%';上記の既定挙動は版や照合順序の設定で変わります。本番環境では各製品の現行ドキュメントとサーバ設定で確認してください。
落とし穴・注意点
LIKE は手軽ですが、実務では混乱しがちな点があります。
NULL の扱い
LIKE で NULL を扱うと、結果は UNKNOWN になります。WHERE 句の中では UNKNOWN は条件不成立として扱われるため、NULL の行は結果から外れます。
-- product_name が NULL の行は、LIKE でも NOT LIKE でもヒットしない
SELECT product_name FROM products WHERE product_name LIKE '%林檎%'; -- NULL は対象外
SELECT product_name FROM products WHERE product_name NOT LIKE '%林檎%'; -- NULL は対象外NULL を結果に含めたい場合は、WHERE product_name IS NULL OR product_name NOT LIKE '%…%' のように明示します。
中間一致・後方一致はインデックスが効きにくい
LIKE のインデックスの効きは、パターンの形と製品の実装で変わります。一般的には次のような傾向があります。
- 前方一致(
'林檎%'):通常の B-tree インデックスが使える可能性があります。 - 中間一致(
'%林檎%')・後方一致('%林檎'):通常の B-tree インデックスでは効きにくく、フルテーブルスキャンになりやすい検索です。
実際の挙動は、列の照合順序、データ型、関数の有無、統計情報、各 DBMS の実行計画によって変わります。件数が大きいテーブルで '%…%' を多用したクエリが遅いと感じたら、EXPLAIN (PostgreSQL / MySQL)や実行プラン表示(SQL Server)で確認します。
中間一致を高速化したい場合の方策は次のようなものがあります。
- 可能なら前方一致だけで済むようにデータの持ち方を変える
- PostgreSQL なら
pg_trgm拡張 + GIN インデックスで中間一致を高速化 - MySQL なら全文検索インデックス(
FULLTEXT)を併用 - そもそも全文検索なら別エンジン(Elasticsearch 等)を検討
詳しい高速化の話はパフォーマンス系の別記事で扱います。
マルチバイト文字・改行・前後の空白
日本語などのマルチバイト文字は、LIKE のパターンでも問題なく使えます。ただしサーバ・クライアントの文字コード設定がズレていると、検索がヒットしない不可解な挙動になります。
列の値に前後の空白や改行が混じっていると、ぱっと見一致しそうな文字列でもヒットしません。怪しいときは TRIM などで一度確認します。
複雑なパターンは正規表現を使う
% と _ の 2 種類だけでは表現できないパターン(「数字 3 桁+ハイフン+数字 4 桁」など)は、LIKE ではなく正規表現の関数・演算子を使います。
- PostgreSQL:
〜演算子、SIMILAR TO - MySQL:
REGEXP/RLIKE - SQL Server:限定的(
PATINDEX/ CLR 関数等)
製品差が大きいので、移植性が必要なときは LIKE で表現できる範囲に収めるのが安全です。
まとめ
LIKE は文字列のあいまい検索の基本ですが、% と _ の意味・前後中央の使い分け・% 自体の検索のためのエスケープ・NULL の扱い・3 製品で違う大文字小文字の挙動、の 5 点を押さえれば、現場のほとんどのケースに対応できます。PostgreSQL の ILIKE や高速化の話は別記事で深掘りします。
