home wiki.fukuchiharuki.me
Menu

キーワード

  • MySQL
  • like
  • PreparedStatement
  • エスケープ
  • ワイルドカード

やりたいこと

PreparedStatementを使って部分一致検索します。エスケープの問題でパッと考えでは分かりづらいところがあります。

方法

SQLを次のように書きます (Javaなどコード上の文字列なので「\」を「\\」と書いています)。

like concat('%', replace(replace(replace(?, '\\\\', '\\\\\\\\'), '%', '\\%'), '_', '\\_'), '%')

詳細

まず次の方法ではダメだということです。

like concat('%', ? '%')

PreparedStatemetは「%」と「_」をエスケープしません。しかし like 検索において「%」と「_」をそのものの文字として指定するには「\%」「\_」にしなければなりません。「%」は正規表現でいうところの「.*」、「_」は正規表現の「.」と解釈されます。

「%」と「_」に対応する

PreparedStatemetは「%」と「_」をエスケープしませんから、like 検索においては自前でエスケープする必要があります。そこで次の方法を考えて、「%」を「\%」に、「_」を「\_」に置換します。

like concat('%', replace(replace(?, '%', '\\%'), '_', '\\_'), '%')

こうすると次の文字列連結を得たことになります。

  • '%'
  • (制御文字を単に文字として投入しつつ「%」と「_」をエスケープした文字列)
  • '%'

すると今度は制御文字を単に文字として投入した「\」が like 検索においてエスケープ文字として機能します。たとえば「\」をバインドすると

like '%\%'

になり、文字「%」で後方一致検索してしまいます。

「\」に対応する

「\」は最終的には「\\」として存在しなければなりません。先の例でいえば

like '%\\%'

になっているべきです。そこでPreparedStatementによってエスケープした「\」(つまりPreparedStatementでバインドした後の「\\」)をさらにエスケープします。それが次の箇所です。

replace(?, '\\\\', '\\\\\\\\')

繰り返しですがJavaなどコード上の文字列なので「\」を「\\」と書いていて「\」がいっぱいになってしまっていますが、意味は単に「\\」を「\\\\」に置換する、です。

エスケープと解釈

エスケープと解釈がどのタイミングではたらいているかを考えればそうかもしれないなと思えてきますね。

参考