前提
あくまでもMysqlだけでの話を前提としてお話します。
先日自分が書いたコードで指摘を受けたため、備忘録として残しておきます。
実際あったコード
ユーザが本のタイトルをLIKE検索したく、下記のようにPDOで実装を行っていました。
$sql = 'select * from books where title like ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array(sprintf('%%%s%%', $title)));
$result = $stmt->fetchAll();
ここで、$title
には、ユーザが入力した検索したい本の名前が入っているものとします。
受けた指摘としては、ユーザが[%]や[_]という文字列を検索したい場合に正しく動作するか?
というものでした。
Mysqlでは、LIKE検索に使われる特殊文字列下記のようなものがあります。
%
-> 任意の0文字以上の文字列
_
-> 任意の1文字
これで問題が起きる場合
例えばユーザが「100%」という文字列を検索したとします。
ユーザとしては、「株式投資で100%成功する方法」とか「告白が100%成功する方法」とかを期待してると思います。
が、実際に実行されるSQLは
select * from books title like '%100%%';
となり、結果は下記のようになります。
-
- 株式投資で100%成功する方法
-
- 告白が100%成功する方法
-
- 最後から100番目の恋
-
- 100回目のプロポーズ
おや、最初の2つは期待どおりですが、最後の2つは期待ハズレのものが返って来てます。
これは、先ほどの、MysqlがLIKE検索で%
が任意の0文字以上の文字列という意味を持っているため、
ユーザが調べようとした100%
の%
は本来の文字列としての%
ではなくて、任意の0文字以上
で判別されてしまって、
100
という文字列を含むタイトルが検索されてしまったのです
じゃあどうするか
↑で説明したとおり、MysqlのLIKE検索において、%
と_
は特殊な意味を持つため、この2つをエスケープして上げる必要があります。
具体的には下記のように。
$sql = 'select * from books where title like ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array(sprintf('%%%s%%', addcslashes($title, '\_%')));
$result = $stmt->fetchAll();
PHPの場合はこれで%
や_
がエスケープされるため、正しく検索が行えるようになります。
とても初歩的な事ですが、ハマってしまった自分が恥ずかしい。。