1. はじめに
MySQLでカンマ区切りのデータを検索する課題
データベースを扱っていると、1つのカラムに複数の値をカンマで区切って保存しているケースに出くわすことがあります。たとえば、"1,3,5"
のような文字列を格納しているカラムがあり、「値3を含むレコードだけを抽出したい」といった場面です。
こうした場合、通常の=
演算子やIN
句では、期待する結果が得られないことがよくあります。なぜなら、カンマ区切りの文字列は“単一の文字列”として扱われるため、値の一部一致ではなく、文字列全体に対する一致で評価されてしまうからです。
FIND_IN_SET関数とは?
このような場面で活躍するのが、MySQLのFIND_IN_SET
関数です。
この関数を使うことで、カンマ区切りの文字列の中から、指定した値が含まれているかどうかを簡単に判定できます。
たとえば、次のようなSQLを考えてみましょう。
SELECT * FROM users WHERE FIND_IN_SET('3', favorite_ids);
このクエリでは、favorite_ids
というカンマ区切りの文字列(例:"1,2,3,4"
)の中に「3」が含まれているレコードを抽出できます。
本記事の目的と対象読者
この記事では、FIND_IN_SET関数の使い方を基礎から丁寧に解説していきます。基本構文から実践例、他の検索方法との違い、注意点、FAQまで、幅広くカバーすることで、実務で役立つ知識を提供することを目的としています。
対象読者は、以下のような方々を想定しています:
- MySQLを日常的に利用しているWebエンジニア・バックエンド開発者
- カンマ区切りのデータを扱う既存のシステムに対応する必要がある方
- SQL初心者で、データの部分一致検索方法に悩んでいる方
2. FIND_IN_SET関数の基本構文と動作
FIND_IN_SETの構文
FIND_IN_SET
は、MySQLでカンマ区切りの文字列内に特定の値が含まれているかを判定する関数です。基本的な構文は次のとおりです:
FIND_IN_SET(検索値, カンマ区切り文字列)
たとえば、以下のように使用します。
SELECT FIND_IN_SET('3', '1,2,3,4'); -- 結果:3
この例では、「3」が3番目にあるため、数値の3が返されます。
返り値のルール
FIND_IN_SET
関数は、以下のようなルールで動作します。
条件 | 結果 |
---|---|
検索値がリストに存在する | リスト内の位置(1から始まる) |
検索値が存在しない | 0 |
いずれかの引数がNULL | NULL |
使用例(位置の返却)
SELECT FIND_IN_SET('b', 'a,b,c'); -- 結果:2
使用例(値が存在しない場合)
SELECT FIND_IN_SET('d', 'a,b,c'); -- 結果:0
使用例(NULLが含まれる場合)
SELECT FIND_IN_SET(NULL, 'a,b,c'); -- 結果:NULL
WHERE句での使用例
通常は、WHERE
句内でのフィルタリングに使用されることが多いです。
SELECT * FROM users WHERE FIND_IN_SET('admin', roles);
この例では、roles
カラムに「admin」という文字列が含まれている行だけが抽出されます。カラムに "user,editor,admin"
のような値が含まれていれば、マッチすることになります。
数値と文字列に関する注意点
FIND_IN_SET
は文字列として比較を行うため、次のような挙動をします:
SELECT FIND_IN_SET(3, '1,2,3,4'); -- 結果:3
SELECT FIND_IN_SET('3', '1,2,3,4'); -- 結果:3
文字列でも数値でも機能しますが、データ型が明確でないと予期せぬ動作を招く可能性があるため、可能な限り明示的に扱うことが望ましいです。
3. 実践的な使用例
カンマ区切りの文字列を持つカラムでの検索
実務では、1つのカラムに複数の値(IDや権限など)をカンマ区切りで保存していることがあります。たとえば、以下のようなusers
テーブルを考えてみましょう。
id | name | favorite_ids |
---|---|---|
1 | 太郎 | 1,3,5 |
2 | 花子 | 2,4,6 |
3 | 次郎 | 3,4,5 |
「3が含まれているユーザーを取得したい」とき、FIND_IN_SET
関数は非常に便利です。
SELECT * FROM users WHERE FIND_IN_SET('3', favorite_ids);
このSQLを実行すると、「太郎」と「次郎」のレコードが抽出されます。
数値型でも問題なく動作する
前項のようにfavorite_ids
が数値のように見える場合でも、FIND_IN_SET
は文字列ベースの比較を行うため、引数にはクォーテーションを付けて文字列として渡すのが確実です。
-- OK
SELECT * FROM users WHERE FIND_IN_SET('5', favorite_ids);
-- 動作はするが、厳密には推奨されない
SELECT * FROM users WHERE FIND_IN_SET(5, favorite_ids);
クエリの可読性と予測可能性を保つため、明示的に文字列として指定することを推奨します。
動的な検索(プレースホルダや変数)
WebアプリケーションなどからSQLを動的に発行する際には、変数やバインドパラメータを使うことが一般的です。
MySQL内での変数を使用する場合は、以下のような形になります。
SET @target_id = '3';
SELECT * FROM users WHERE FIND_IN_SET(@target_id, favorite_ids);
PHPやPython、Node.jsなどのアプリケーション側からバインドする際も、同様の形式でプレースホルダを使って対応可能です。
複数値を検索したいときの工夫
残念ながら、FIND_IN_SET
は1つの値しか検索できません。
「3または4を含むレコードを取り出したい」場合は、OR
を使って複数回書く必要があります。
SELECT * FROM users
WHERE FIND_IN_SET('3', favorite_ids) OR FIND_IN_SET('4', favorite_ids);
あるいは、もっと複雑な条件になる場合は、アプリケーション側でSQL文を動的に構築するか、正規化された別テーブルへの移行を検討すべき場面です。
4. FIND_IN_SET関数と他の検索方法との比較
よく比較される検索方法:IN句とLIKE句
MySQLで特定の値が含まれているかを調べるには、FIND_IN_SET
以外にもIN句
やLIKE句
が使われることがあります。ただし、それぞれの使いどころや動作は異なり、誤って使うと正しい検索結果が得られないこともあります。
ここでは、FIND_IN_SETとの違いや使い分けのポイントを明確にしておきましょう。
IN句との比較
IN
句は通常、複数の定数値に対して一致するかどうかを調べる際に使われます。
-- IN句の例(favorite_idsが"3"に一致するレコードを探すわけではない)
SELECT * FROM users WHERE favorite_ids IN ('3');
この場合、favorite_ids
に「3」が完全一致するレコードしかヒットしません。つまり、"1,3,5"
のような値はヒットせず、"3"
という文字列のみ対象になります。
対して、FIND_IN_SET
は部分一致ではなく、カンマで区切られた中の要素の位置を判断するため、以下のような使い方で「3を含むレコード」を正確に抽出できます。
SELECT * FROM users WHERE FIND_IN_SET('3', favorite_ids);
✅ 使い分けポイント:
IN
句:正規化されたテーブルで使用(例:SELECT * FROM posts WHERE category_id IN (1, 3, 5)
)FIND_IN_SET
:非正規化されたカンマ区切り文字列に対して使用
LIKE句との比較
LIKE
句を使って部分一致を判定することも技術的には可能ですが、以下のような注意点があります。
-- 間違いやすいLIKE句の例
SELECT * FROM users WHERE favorite_ids LIKE '%3%';
このクエリは、「3を含む」ではなく、「3という文字が含まれる文字列すべて」を対象にしてしまうため、"13"
, "23"
, "30"
などもマッチしてしまう可能性があります。
これでは、3が単独で含まれていることを検出できません。
✅ 使い分けポイント:
LIKE
句:文字列の曖昧検索に有効だが、値の区切りを認識できないFIND_IN_SET
:カンマ区切りの中で、単独の値の一致を正確に確認可能
パフォーマンスの観点での違い
比較対象 | インデックス利用 | 処理の対象 | 高速性 |
---|---|---|---|
IN 句 | 利用可能 | 数値 or 単一値 | ◎ 非常に速い |
LIKE 句 | パターンによる | 全文検索 | △ 条件により遅くなる |
FIND_IN_SET | 利用不可 | 全件スキャン | × 遅い可能性あり |
特にFIND_IN_SET
はインデックスを利用できず、フルスキャンになりがちです。大量のデータを扱う場合は、構造の見直しが必要になります。
5. 注意点とベストプラクティス
カンマを含む値には非対応
FIND_IN_SET
関数は、カンマで区切られた「単純な値のリスト」を前提としています。そのため、リストの中の各要素自体にカンマが含まれていると、意図した通りに動作しません。
NGの例:
SELECT FIND_IN_SET('1,2', '1,2,3,4'); -- 結果:1
このような使い方をすると、文字列全体が誤って一致してしまうため、値にカンマが含まれないことを保証できる構造でのみ使うことが前提となります。
パフォーマンス上の問題
FIND_IN_SET
はインデックスが使えず、テーブルの全レコードを走査(フルテーブルスキャン)します。したがって、大規模なテーブルで使用すると、検索速度が極端に遅くなる可能性があります。
回避策:
- カンマ区切りのデータではなく、リレーションを正規化して別テーブルで管理する。
- パフォーマンス重視の環境では、一時的なテーブル展開やJOIN戦略を検討する。
例:user_favorites
のような中間テーブルを作成して管理すれば、以下のようにインデックスを活かした高速検索が可能になります。
SELECT users.*
FROM users
JOIN user_favorites ON users.id = user_favorites.user_id
WHERE user_favorites.favorite_id = 3;
可読性・保守性の観点
一見便利に思えるFIND_IN_SET
ですが、以下のような問題が付きまといます:
- クエリが直感的でない(位置で返る)
- 値の追加・削除が難しい
- データ整合性を担保しづらい(1つのカラムに複数の意味を持たせてしまう)
そのため、システムの保守性やデータ整合性を重視する場合には、構造を見直すこと自体が“ベストプラクティス”となるケースも少なくありません。
どうしてもFIND_IN_SETを使う必要があるとき
既存のシステムや他社製品のデータ構造など、どうしてもカンマ区切りのカラムを扱わなければならない場面もあります。その場合は、次のような対策を講じましょう。
- 検索対象の件数を減らすフィルターを先にかける
- 文字列の整形ミス(ダブルカンマや前後のスペース)を防ぐ
- 可能であればアプリケーション側での補助処理を行う
6. よくある質問(FAQ)
FIND_IN_SET関数はインデックスを利用できますか?
いいえ、FIND_IN_SET関数はインデックスを利用できません。この関数は内部的に文字列を分割して検索する処理を行っており、MySQLのインデックス最適化の恩恵を受けることができません。
そのため、大規模なテーブルに対して使用すると、検索速度が低下する可能性があります。性能が求められるシステムでは、構造の見直しや正規化を検討すべきです。
数値と文字列が混在していても正しく動作しますか?
基本的には動作しますが、比較は文字列として行われるため、数値と文字列が混在していると意図しない挙動になることがあります。
例えば、以下のような例では、両者ともに3
と一致した結果になります:
SELECT FIND_IN_SET(3, '1,2,3,4'); -- 結果:3
SELECT FIND_IN_SET('3', '1,2,3,4'); -- 結果:3
ただし、FIND_IN_SET('03', '01,02,03')
のようなケースでは、ゼロ埋めされた文字列が一致しないこともあるため、値のフォーマットは統一しておくのが安全です。
複数の値を一度に検索する方法はありますか?
FIND_IN_SET
は単一の検索値しか受け付けないため、「3 または 4 を含むレコードを検索したい」といった場合には、以下のようにOR
で複数回呼び出す必要があります。
SELECT * FROM users
WHERE FIND_IN_SET('3', favorite_ids)
OR FIND_IN_SET('4', favorite_ids);
あるいは、もっと複雑な条件になる場合は、アプリケーション側でSQL文を動的に構築するか、正規化された別テーブルへの移行を検討すべき場面です。
FIND_IN_SETを使うとパフォーマンスが悪くなります。対処法は?
以下の対策が有効です:
- 正規化されたテーブル設計に切り替える
- 検索対象を限定するためのフィルタ条件を先に適用する
- データ量が少ない場合のみ限定的に使用する
- 全文検索やJSON型など、より構造化されたデータ表現への移行を検討する
現代のMySQLでは、JSON型を使ったデータ管理も選択肢の一つです。たとえば、roles
カラムをJSON配列として管理すれば、JSON_CONTAINS()
で柔軟かつ高速に検索できます。
FIND_IN_SETは将来的に非推奨になりますか?
現時点(MySQL 8.0)では、FIND_IN_SET
関数が非推奨とされている事実はありません。ただし、非正規化されたデータ構造(カンマ区切りのカラム)自体が推奨されていないため、将来的に利用機会は減っていくと考えられます。
データベース設計を見直す機会があれば、正規化やJSON型の活用を前提に設計することが理想的です。
7. まとめ
FIND_IN_SETの特徴と利点の再確認
FIND_IN_SET
関数は、MySQLでカンマ区切りの文字列を検索する際に非常に便利な関数です。特に、複数の値が格納されている1つのカラムから、特定の値を含むレコードを抽出したいときに活躍します。
シンプルな構文で使用でき、LIKE
句やIN
句では実現しにくい「単独の値としての一致」をチェックできる点が最大の強みです。
使用時の注意点
一方で、以下のような制限や注意点もあるため、安易に使いすぎないことも重要です。
- インデックスが効かない(=検索が遅くなる)
- カンマを含む値には非対応
- 正規化されていない構造が前提
- 単一値しか検索できない(複数検索には
OR
条件が必要)
これらの特性を理解したうえで、適切な場面で使用することが求められます。
こんなときに使うべき、使うべきでない
状況 | 使用すべきか | 理由 |
---|---|---|
小規模なデータ、検索回数が少ない | ✅ 使用OK | 実装が簡単でコストが低い |
既存システムに依存した構造 | ✅ 限定的に使用 | 改修が難しい場合に有効 |
大規模データ・高頻度アクセス | ❌ 推奨しない | パフォーマンスの低下が顕著 |
正規化された構造に変更できる | ❌ 非推奨 | JOINや中間テーブルの方が効率的 |
実務への活かし方
- 既存のDB構造を前提に柔軟に対応する術として知っておく
- 将来的には正規化されたデータ設計を採用する判断材料に
- 応急処置的に使うのではなく、「この関数で何をしているのか」を正確に理解する
特に、保守性や可読性を重視した開発スタイルを目指す方には、「一時的に使うが、いずれ卒業する」関数として覚えておくとよいでしょう。