- SQL
SQL LIKE部分一致とエスケープ|%・_の扱いをDB別にコピペ解説
SQLのLIKEとは、%(0文字以上の任意の文字列)と _(任意の1文字)という2つのワイルドカードで、文字列のパターン一致を調べる演算子です。 name LIKE '%ジュース%' と書けば「ジュース」を含む行、name LIKE 'apple%' と書けば「apple」で始まる行が取り出せます。
ただし、使い込むと次のような疑問が出てきます。「50% という文字そのものを探したいときは?」「同じSQLなのにMySQLとPostgreSQLで件数が違う」「% から始まる検索は遅い?」。この記事では、PostgreSQL 16.15・MySQL 8.0.46・SQLite 3.45.1 で同じSQLを実行した結果を並べて、基本の書き方からエスケープ、大文字小文字、アプリからの安全な渡し方、インデックスまでを一通り確認します。上位の解説記事にあまりない「DB別の既定エスケープ文字」「照合順序による件数の違い」「EXPLAINでの確認」を、実際の出力つきで載せています。
MySQL 8.0 は2026年4月にサポートが終了しています。本記事の検証は手元の 8.0.46 で行いましたが、ここで扱うLIKE・ESCAPE・照合順序・EXPLAINの書き方は MySQL 8.4/9.7 でも同じ手順で使えます(違いがある箇所は本文で明記します)。
SQL LIKEの早見表
| やりたいこと | 書き方 | 一致する例 | 一致しない例 |
|---|---|---|---|
| 前方一致(〜で始まる) | LIKE 'apple%' |
apple pie | Pineapple |
| 後方一致(〜で終わる) | LIKE '%ジュース' |
グレープジュース | ジュースセット |
| 部分一致(〜を含む) | LIKE '%ジュース%' |
ジュースセット | ジューサー |
| 任意の1文字 | LIKE 'user_id' |
user_id、userXid | user__id |
| 含まない | NOT LIKE '%ジュース%' |
apple pie | グレープジュース |
| 複数キーワードのどれか | LIKE '%a%' OR LIKE '%b%' |
a か b を含む | どちらも含まない |
| 複数キーワードすべて | LIKE '%a%' AND LIKE '%b%' |
a と b を含む | 片方だけ |
% そのものを探す |
LIKE '50!%%' ESCAPE '!' |
50%オフ | 500円引き |
_ そのものを探す |
LIKE 'user!_id' ESCAPE '!' |
user_id | userXid |
| 大文字小文字を無視(PostgreSQL) | ILIKE '%juice%' |
Apple Juice | — |
| 大文字小文字を区別(MySQL) | LIKE '%Juice%' COLLATE utf8mb4_bin |
Apple Juice | apple juice |
DB別の違い(要点)
| 項目 | PostgreSQL 16 | MySQL 8.0 | SQLite 3.45 | SQL Server | Oracle |
|---|---|---|---|---|---|
| 既定のエスケープ文字 | \ |
\ |
なし | なし | なし |
| ESCAPE句 | 対応 | 対応 | 対応 | 対応 | 対応 |
[ ] で文字集合 |
なし | なし | なし | [%]、[a-c] |
なし |
| 大文字小文字(既定) | 区別する | 照合順序しだい(既定は区別しない) | ASCIIのみ区別しない | 照合順序しだい | 区別する |
| 区別しない検索 | ILIKE |
既定の _ci 照合順序 |
既定の LIKE | _CI 照合順序 |
UPPER() 同士で比較など |
| 前方一致でB-tree索引 | C照合 or *_pattern_ops |
効く | 条件つき | 効く | 効く |
(SQL Server・Oracle の列は公式ドキュメントで確認した内容で、本記事の環境では実行していません。)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQL LIKE部分一致とエスケープ|%・_の扱いをDB別にコピペ解説」
サンプルデータを用意する
以下の表で全DB共通のSQLを動かします。そのまま貼り付けて試せます。
DROP TABLE IF EXISTS items;
CREATE TABLE items (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO items VALUES
(1,'Apple Juice'),(2,'apple pie'),(3,'Pineapple'),(4,'グレープジュース'),
(5,'ジュースセット'),(6,'50%オフ'),(7,'500円引き'),(8,'user_id'),(9,'userXid'),(10,'C:\temp');
MySQLだけは 'C:\temp' の \t がタブ文字に変換されて保存されます。 MySQLの文字列リテラルではバックスラッシュがエスケープ文字だからです。MySQLでは 'C:\\temp' と2つ重ねて書いてください。また、mysqlクライアントの文字コードが latin1 のままだと日本語の比較結果が変わるため、mysql --default-character-set=utf8mb4 で接続します。
前方一致・後方一致・部分一致を書く
基本の3パターン
SELECT id, name FROM items WHERE name LIKE 'apple%' ORDER BY id; -- 前方一致
SELECT id, name FROM items WHERE name LIKE '%ジュース' ORDER BY id; -- 後方一致
SELECT id, name FROM items WHERE name LIKE '%ジュース%' ORDER BY id; -- 部分一致
実行結果(3つのDBで比較)
| SQL | PostgreSQL 16 | MySQL 8.0 | SQLite 3.45 |
|---|---|---|---|
LIKE 'apple%' |
2 apple pie | 1 Apple Juice、2 apple pie | 1 Apple Juice、2 apple pie |
LIKE '%ジュース' |
4 グレープジュース | 4 グレープジュース | 4 グレープジュース |
LIKE '%ジュース%' |
4、5 | 4、5 | 4、5 |
前方一致の件数がDBによって違います。 PostgreSQLはLIKEで大文字小文字を区別し、MySQL(既定の照合順序 utf8mb4_0900_ai_ci)とSQLiteは区別しないためです。詳しくは後半の「大文字小文字」の章で確認します。
_(アンダースコア)で1文字だけ任意にする
SELECT id, name FROM items WHERE name LIKE 'user_id' ORDER BY id;
id | name
----+---------
8 | user_id
9 | userXid
(2 rows)
_ は「ちょうど1文字」のワイルドカードです。 user_id という列名やコードをそのまま条件に書くと、userXid のような値まで一致します。_ を文字として探す方法は次の章で扱います。
NOT LIKE で除外する
SELECT id, name FROM items WHERE name NOT LIKE '%ジュース%' ORDER BY id;
結果は3つのDBとも id 1〜3、6〜10 の8件でした。NOT LIKE は NULL の行を返しません。 name が NULL の行は LIKE でも NOT LIKE でも結果が「不明」になるため、NULLも含めたいときは name NOT LIKE '%ジュース%' OR name IS NULL と書きます。
複数条件(OR/AND)
-- どちらかを含む
SELECT id, name FROM items WHERE name LIKE '%pie%' OR name LIKE '%ジュース' ORDER BY id;
-- すべてを含む(p を含み、かつ e で終わる)
SELECT id, name FROM items WHERE name LIKE '%p%' AND name LIKE '%e' ORDER BY id;
| 条件 | 結果(3DB共通) |
|---|---|
'%pie%' OR '%ジュース' |
2 apple pie、4 グレープジュース |
'%p%' AND '%e' |
1 Apple Juice、2 apple pie、3 Pineapple |
キーワードが多いときの書き方もDBごとに用意されています。
| DB | 書き方 |
|---|---|
| PostgreSQL | name LIKE ANY (ARRAY['%pie%', '%ジュース'])(すべてなら LIKE ALL) |
| MySQL | name REGEXP 'pie|ジュース$'(正規表現) |
| 共通 | OR を並べる。AND と混ぜるときは ( ... OR ... ) と括弧で囲む |
WHERE category = 1 AND name LIKE '%a%' OR name LIKE '%b%' のように括弧を忘れると、AND が先に評価されて意図しない行まで返ります。OR を含む条件は括弧で囲むと覚えておくと安心です。
%や_を含む文字列を検索する(エスケープ)
エスケープしないとどうなるか
「50%」という文字を含む商品を探すつもりで LIKE '50%' と書くと、% がワイルドカードとして働きます。
SELECT id, name FROM items WHERE name LIKE '50%' ORDER BY id;
id | name
----+-----------
6 | 50%オフ
7 | 500円引き
(2 rows)
「50で始まる」という意味になり、500円引き も一致します。 % や _ を文字として扱うには、直前にエスケープ文字を置きます。
ESCAPE句を使う(全DB共通の書き方)
SELECT id, name FROM items WHERE name LIKE '50!%%' ESCAPE '!' ORDER BY id;
SELECT id, name FROM items WHERE name LIKE 'user!_id' ESCAPE '!' ORDER BY id;
実行結果(PostgreSQL 16・MySQL 8.0・SQLite 3.45 すべて同じ)
id | name
----+---------
6 | 50%オフ
(1 row)
id | name
----+---------
8 | user_id
(1 row)
'50!%%' は「!% =文字としての%」+「% =ワイルドカード」です。ESCAPE句で ! や # のような記号を指定する書き方が、どのDBでも同じ結果になるのでおすすめです。 バックスラッシュは文字列リテラル側でも特別な意味を持つDBがあり、書き方がDBごとに変わるためです。
既定のエスケープ文字(ESCAPE句なし)をDB別に比べる
ESCAPE句を書かずに \ でエスケープしたときの結果です。
| SQL | PostgreSQL 16 | MySQL 8.0 | SQLite 3.45 |
|---|---|---|---|
LIKE '50\%%' |
6 50%オフ | 6 50%オフ | 0件 |
LIKE 'user\_id' |
8 user_id | 8 user_id | 0件 |
PostgreSQLとMySQLは \ が既定のエスケープ文字、SQLiteには既定のエスケープ文字がありません。 SQLiteでは \ がただの文字として比較されるので、50\ で始まる行を探すことになり0件です。SQLiteでは必ず ESCAPE 句を書きます。
SQL Server と Oracle は公式ドキュメントで確認しました(本記事では未実行)。
| DB | 文字として%を探す書き方 | 公式ドキュメントの記載 |
|---|---|---|
| SQL Server | LIKE '5[%]'、LIKE '%30!%%' ESCAPE '!' |
ワイルドカードを [ ] で囲むと文字として扱える。ESCAPE句も使える |
| Oracle | LIKE '%A\_B%' ESCAPE '\' |
ESCAPE を指定しない場合、既定のエスケープ文字はない |
SQL Server では [ 自体も特別な文字([a-c] のような文字集合)になる点が他のDBと異なります。
バックスラッシュそのものを探す
C:\temp のようにバックスラッシュを含む値を探す場合です。
| DB | 書き方 | 結果 |
|---|---|---|
| PostgreSQL 16 | LIKE 'C:!\%' ESCAPE '!' |
10 C:\temp |
| SQLite 3.45 | LIKE 'C:!\%' ESCAPE '!' |
10 C:\temp |
| MySQL 8.0 | LIKE 'C:\\\\%' |
10 C:\temp |
| MySQL 8.0 | LIKE 'C:\\%' ESCAPE '!' |
10 C:\temp |
| MySQL 8.0 | LIKE 'C:\\%'(ESCAPEなし) |
0件 |
MySQLは「文字列リテラル」と「LIKEのパターン」の2段階でバックスラッシュが解釈されます。 'C:\\%' はリテラルの段階で C:\% になり、パターンの段階で \% が「文字としての%」になるため0件です。ESCAPEを ! に変えると \ がただの文字になり、意図どおり一致します。
MySQLの NO_BACKSLASH_ESCAPES モード
MySQL の sql_mode に NO_BACKSLASH_ESCAPES を加えると、バックスラッシュは文字列リテラルでもLIKEでもエスケープ文字ではなくなります。
SET SESSION sql_mode = CONCAT(@@sql_mode, ',NO_BACKSLASH_ESCAPES');
SELECT id, name FROM items WHERE name LIKE '50\%%'; -- 0件
SELECT id, name FROM items WHERE name LIKE '50!%%' ESCAPE '!'; -- 6 50%オフ
SELECT id, name FROM items WHERE name LIKE 'C:\%'; -- 10 C:\temp
サーバー設定によって \ の意味が変わるため、ESCAPE句で記号を明示しておくと設定に左右されません。
大文字小文字・ひらがなカタカナの区別
サンプルに (11,'じゅーす'),(12,'juice'),(13,'ÄPFEL') を追加して比べました。
PostgreSQL:LIKE は区別、ILIKE は区別しない
SELECT id, name FROM items WHERE name LIKE '%juice%'; -- 0件
SELECT id, name FROM items WHERE name ILIKE '%juice%'; -- 1 Apple Juice
SELECT id, name FROM items WHERE lower(name) LIKE lower('%JUICE%'); -- 1 Apple Juice
ILIKE はPostgreSQL独自の「大文字小文字を区別しないLIKE」です(演算子では ~~*)。全角の juice は半角とは別の文字なので、ILIKEでも一致しません。他のDBへ移す可能性があるSQLでは lower() 同士で比べる書き方が使いやすいです。
MySQL:照合順序(collation)で決まる
| SQL(MySQL 8.0.46、列は utf8mb4_0900_ai_ci) | 結果 |
|---|---|
LIKE '%juice%' |
1 Apple Juice、12 juice |
LIKE '%ジュース%' |
4、5、11 じゅーす |
LIKE 'apfel' |
13 ÄPFEL |
LIKE '%juice%' COLLATE utf8mb4_bin |
0件 |
LIKE '%Juice%' COLLATE utf8mb4_0900_as_cs |
1 Apple Juice |
LIKE '%ジュース%' COLLATE utf8mb4_bin |
4、5 |
MySQLの既定の照合順序 utf8mb4_0900_ai_ci は、大文字小文字・アクセント・全角半角・ひらがなカタカナを同じ文字として比べます。 あいまいに探したい検索窓には便利で、厳密に探したいときは COLLATE utf8mb4_bin を付けます。
検証中に気づいた点として、MySQL 8.0.46 では日本語用の utf8mb4_ja_0900_as_cs_ks(かな区別あり)を指定しても、LIKE では ジュース と じゅーす が一致しました。
SELECT 'ジュース' = 'じゅーす' COLLATE utf8mb4_ja_0900_as_cs_ks AS eq_ks,
'ジュース' LIKE 'じゅーす' COLLATE utf8mb4_ja_0900_as_cs_ks AS like_ks,
'ジュース' LIKE 'じゅーす' COLLATE utf8mb4_bin AS like_bin;
+-------+---------+----------+
| eq_ks | like_ks | like_bin |
+-------+---------+----------+
| 0 | 1 | 0 |
+-------+---------+----------+
= ではかなを区別し、LIKE では区別しない結果でした。 ひらがなとカタカナを確実に分けてLIKE検索したいときは utf8mb4_bin を使うと意図どおりになります。照合順序そのものの比較は「MySQL照合順序(collation)の違い」の記事で詳しくまとめています。
SQLite:ASCIIの英字だけ区別しない
SELECT id, name FROM items WHERE name LIKE '%juice%'; -- 1 Apple Juice
SELECT id, name FROM items WHERE name LIKE 'äpfel'; -- 0件(Ä と ä は別扱い)
PRAGMA case_sensitive_like = ON;
SELECT id, name FROM items WHERE name LIKE '%juice%'; -- 0件
SELECT id, name FROM items WHERE name GLOB '*Juice*'; -- 1 Apple Juice
SQLiteのLIKEはASCIIの英字だけ大文字小文字を同一視します。 PRAGMA case_sensitive_like = ON で区別するように切り替えられ、GLOB(* と ? を使うパターン)は常に区別します。
大文字小文字の判断表
| 目的 | PostgreSQL | MySQL | SQLite |
|---|---|---|---|
| 区別せずに探す | ILIKE または lower() |
既定(_ci)のまま |
既定のまま(英字のみ) |
| 区別して探す | 既定の LIKE |
COLLATE utf8mb4_bin |
GLOB または PRAGMA |
| 全角半角・かなもまとめて探す | 正規化した列を別に持つ | utf8mb4_0900_ai_ci |
正規化した列を別に持つ |
アプリからLIKEを使う(プレースホルダとエスケープ関数)
検索窓の入力を条件にするときは、SQLを文字列連結せずプレースホルダ(? や %s)で渡し、そのうえで % _ をエスケープするのが基本です。プレースホルダはSQLインジェクションを防ぎますが、% や _ はLIKEのワイルドカードとしてそのまま働くため、「50%」と入力すると 500円引き まで一致します。
Python(sqlite3)
import sqlite3
def escape_like(s: str, esc: str = "!") -> str:
"""LIKE のワイルドカード(% と _)とエスケープ文字自体をエスケープする"""
return (s.replace(esc, esc + esc)
.replace("%", esc + "%")
.replace("_", esc + "_"))
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)")
conn.executemany("INSERT INTO items(name) VALUES (?)",
[("50%オフ",), ("500円引き",), ("user_id",), ("userXid",), ("C:\\temp",)])
for keyword in ["50%", "user_id", "C:\\"]:
raw = conn.execute("SELECT name FROM items WHERE name LIKE ? ORDER BY id",
(f"%{keyword}%",)).fetchall()
safe = conn.execute("SELECT name FROM items WHERE name LIKE ? ESCAPE '!' ORDER BY id",
(f"%{escape_like(keyword)}%",)).fetchall()
print(f"{keyword!r:12} エスケープなし={[r[0] for r in raw]} エスケープあり={[r[0] for r in safe]}")
実行結果(Python 3.11)
'50%' エスケープなし=['50%オフ', '500円引き'] エスケープあり=['50%オフ']
'user_id' エスケープなし=['user_id', 'userXid'] エスケープあり=['user_id']
'C:\\' エスケープなし=['C:\\temp'] エスケープあり=['C:\\temp']
エスケープ文字自体(ここでは !)を最初に置き換えるのがポイントです。順番を逆にすると、% を !% に変えたあとでその ! が !! になり、意味が変わってしまいます。
同じ escape_like() と ESCAPE '!' を、psycopg 3.3.6(PostgreSQL 16)と PyMySQL(MySQL 8.0)でも実行しました。
SQL = "SELECT name FROM items WHERE name LIKE %s ESCAPE '!' ORDER BY id"
cur.execute(SQL, (f"%{escape_like(keyword)}%",))
PostgreSQL '50%' → ['50%オフ']
PostgreSQL 'user_id' → ['user_id']
PostgreSQL 'C:\\' → ['C:\\temp']
MySQL '50%' → ['50%オフ']
MySQL 'user_id' → ['user_id']
MySQL 'C:\\' → ['C:\\temp']
ESCAPE文字を ! にそろえると、同じ関数がSQLite・PostgreSQL・MySQLのどれでもそのまま使えました。
JavaScript(Node.js 22 の node:sqlite)
import { DatabaseSync } from 'node:sqlite';
// LIKE のワイルドカード(% と _)とエスケープ文字自体をエスケープする
const escapeLike = (s, esc = '!') =>
s.replaceAll(esc, esc + esc).replaceAll('%', esc + '%').replaceAll('_', esc + '_');
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
const ins = db.prepare('INSERT INTO items(name) VALUES (?)');
for (const n of ['50%オフ', '500円引き', 'user_id', 'userXid', 'C:\\temp']) ins.run(n);
const stmt = db.prepare("SELECT name FROM items WHERE name LIKE ? ESCAPE '!' ORDER BY id");
for (const keyword of ['50%', 'user_id', 'C:\\']) {
const rows = stmt.all(`%${escapeLike(keyword)}%`).map(r => r.name);
console.log(keyword.padEnd(8), '→', rows);
}
実行結果(Node.js v22.22.2、node like_escape.mjs)
50% → [ '50%オフ' ]
user_id → [ 'user_id' ]
C:\ → [ 'C:\\temp' ]
Node.js 22 の node:sqlite は実行時に ExperimentalWarning を表示します。pg や mysql2 など他のドライバでも、escapeLike() とプレースホルダの組み合わせは同じです。SQL Server を使う場合は [ もエスケープ対象に加えると、[a-c] のような文字集合として解釈されるのを防げます。
インデックスが効くLIKE・効かないLIKE
判断表
| パターン | B-treeインデックス | 理由 |
|---|---|---|
LIKE 'P00123%'(前方一致) |
使える | 先頭が決まるので範囲検索に変換できる |
LIKE '%0123'(後方一致) |
使えない | 先頭が決まらず全件を調べる |
LIKE '%0123%'(部分一致) |
使えない | 同上 |
LIKE 'P_0123%' |
先頭の P までの範囲で使える |
_ の手前までが範囲になる |
MySQL 8.0 の EXPLAIN
20万行の products(code) にインデックスを作って比べました。
CREATE INDEX idx_code ON products(code);
EXPLAIN ANALYZE SELECT * FROM products WHERE code LIKE 'P00123%';
EXPLAIN ANALYZE SELECT * FROM products WHERE code LIKE '%0123%';
-> Index range scan on products using idx_code over ('P00123' <= code <= 'P00123????...'),
with index condition: (products.`code` like 'P00123%') (cost=45.3 rows=100) (actual time=0.037..0.225 rows=100 loops=1)
-> Filter: (products.`code` like '%0123%') (cost=20146 rows=22177) (actual time=0.122..68.4 rows=1140 loops=1)
-> Table scan on products (cost=20146 rows=199613) (actual time=0.0894..40.5 rows=200000 loops=1)
(1行目の ???? は長いため途中を省略しています。)前方一致は Index range scan、% 始まりは Table scan で20万行すべてを読んでいます。
PostgreSQL 16 の EXPLAIN(照合順序に注意)
CREATE INDEX idx_code ON products(code);
EXPLAIN SELECT * FROM products WHERE code LIKE 'P00123%';
Seq Scan on products (cost=0.00..4166.00 rows=20 width=34)
Filter: ((code)::text ~~ 'P00123%'::text)
PostgreSQLでは、データベースの照合順序が C 以外(今回は C.UTF-8)だと、通常のB-treeインデックスはLIKEの前方一致に使われません。 varchar_pattern_ops(text型なら text_pattern_ops)を付けたインデックスを作ると使われます。
CREATE INDEX idx_code_pattern ON products(code varchar_pattern_ops);
EXPLAIN ANALYZE SELECT * FROM products WHERE code LIKE 'P00123%';
Index Scan using idx_code_pattern on products (cost=0.42..8.44 rows=20 width=34) (actual time=0.032..0.048 rows=100 loops=1)
Index Cond: (((code)::text ~>=~ 'P00123'::text) AND ((code)::text ~<~ 'P00124'::text))
Filter: ((code)::text ~~ 'P00123%'::text)
Execution Time: 0.073 ms
'P00123%' が >= 'P00123' AND < 'P00124' の範囲検索に変換されているのがわかります。同じ表で LIKE '%0123%' は Seq Scan(Rows Removed by Filter: 198860、Execution Time: 39.563 ms)でした。
部分一致を速くする代替手段
| 手段 | DB | 向いている検索 |
|---|---|---|
| pg_trgm + GINインデックス | PostgreSQL | LIKE '%..%'、ILIKE をそのまま速くしたい |
| FULLTEXT + ngramパーサ | MySQL | 日本語のキーワード検索(MATCH ... AGAINST) |
| 検索用の列を別に持つ | 共通 | 後方一致は文字列を反転した列+前方一致 |
pg_trgm で LIKE '%ジュース%' を実行した結果です(PostgreSQL 16、20万行中2,000件一致)。
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_name_trgm ON products USING gin (name gin_trgm_ops);
EXPLAIN ANALYZE SELECT count(*) FROM products WHERE name LIKE '%ジュース%';
-> Bitmap Heap Scan on products (cost=21.59..95.27 rows=20 width=0) (actual time=0.808..2.141 rows=2000 loops=1)
Recheck Cond: (name ~~ '%ジュース%'::text)
-> Bitmap Index Scan on idx_name_trgm (cost=0.00..21.59 rows=20 width=0) (actual time=0.299..0.299 rows=2000 loops=1)
Index Cond: (name ~~ '%ジュース%'::text)
Execution Time: 2.275 ms
% 始まりのLIKEのまま、インデックスが使われました。 ILIKE '%ジュース%' でも同じGINインデックスが使われ、Execution Time は 2.551 ms でした。pg_trgm は3文字単位で索引を作るため、2文字以下の検索語では効果が出にくい点に注意します。MySQLでは ALTER TABLE products ADD FULLTEXT INDEX ft_name (name) WITH PARSER ngram; を作成し、MATCH(name) AGAINST('ジュース' IN BOOLEAN MODE) で LIKE と同じ2,000件が返ることを確認しました。全文検索の詳しい比較やインデックスが使われない他のパターンは、「PostgreSQL日本語全文検索」「SQLのインデックスが効かない10のパターン」の記事で扱っています。
よくあるつまずきと直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
50% で探すと 500 も出る |
% がワイルドカード |
LIKE '50!%%' ESCAPE '!' |
user_id で userXid も出る |
_ がワイルドカード |
LIKE 'user!_id' ESCAPE '!' |
SQLiteで \% が効かない |
SQLiteは既定のエスケープ文字なし | ESCAPE 句を書く |
MySQLで C:\temp が見つからない |
INSERT時に \t がタブになった |
'C:\\temp' で保存し直す |
| 同じSQLでDBごとに件数が違う | 大文字小文字・照合順序の差 | ILIKE、COLLATE、lower() で明示 |
| MySQLで日本語の一致がおかしい | クライアントの文字コードが latin1 | --default-character-set=utf8mb4 |
| 前方一致なのにSeq Scan(PostgreSQL) | 照合順序が C 以外 | text_pattern_ops のインデックス |
| NOT LIKE で NULL 行が消える | NULL との比較は不明になる | OR col IS NULL を足す |
よくある質問
Q. LIKEと = はどちらが速いですか?
A. ワイルドカードを含まない LIKE 'abc' は = 'abc' とほぼ同じ扱いになることが多いですが、意図が伝わりやすいので完全一致は = で書くのがおすすめです。末尾の空白の扱いなど細かな違いもあります。
Q. ESCAPE に使う文字は何がよいですか?
A. 検索対象のデータにあまり出てこない1文字の記号(!、#、^ など)が扱いやすいです。本記事の escape_like() のようにエスケープ文字自体もエスケープしておけば、データに含まれていても正しく検索できます。
Q. 正規表現(REGEXP、~)とLIKEはどう使い分けますか?
A. 「含む」「始まる」程度ならLIKEが読みやすく、前方一致ならインデックスも使えます。「数字が3桁続く」のような複雑な条件は正規表現が向いています。
Q. MySQL 8.4 や 9.7 でも同じ結果になりますか?
A. 本記事で扱ったLIKE・ESCAPE・NO_BACKSLASH_ESCAPES・照合順序の指定方法は、8.4/9.7でも同じ手順で使えます。照合順序ごとの細かな比較結果はバージョンで変わる可能性があるため、重要な検索は利用中のバージョンで本記事のSQLを一度実行して確認すると確実です。
まとめ
- LIKEのワイルドカードは
%(0文字以上)と_(1文字)の2つ。前方・後方・部分一致は%の位置で決まる %や_を文字として探すときはESCAPE '!'を明示すると、PostgreSQL・MySQL・SQLiteで同じ結果になる- 既定のエスケープ文字はPostgreSQLとMySQLが
\、SQLite・SQL Server・Oracleはなし。SQL Serverは[%]も使える - 大文字小文字の扱いはDBごとに異なる。PostgreSQLは
ILIKE、MySQLはCOLLATE、SQLiteは PRAGMA で切り替える - アプリではプレースホルダ+エスケープ関数。エスケープ文字自体を最初に置き換える
- インデックスが効くのは前方一致。PostgreSQLは照合順序によって
text_pattern_opsが必要 - 部分一致を速くしたいときは pg_trgm や FULLTEXT(ngram) を検討する
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SQL LIKE部分一致とエスケープ|%・_の扱いをDB別にコピペ解説」
株式会社RJCでは、SQLの検索の書き方やDBごとの違いを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。