はじめに
今回からは,とうとう応用編です。 ConditionBean/OutsideSql(外だしSQL)両方における 「エスケープ付き曖昧検索」を見て行きます。
エスケープ付き曖昧検索
エスケープ付き曖昧検索とは?
まずは基本的な概念から説明します。
曖昧検索は,条件値に含まれているワイルドカード(%や_)を使って, 前方一致・中間一致等を実現します。例えば,条件値が'ス%'であれば 「スで始まるもの」,'%ス%'であれば「スを含むもの」という検索に なります。しかし,もしデータベース上に「100%ジュースの飲み物」という文字が 格納されていて,「'100%ジュース'を含むもの」という検索をしたい場合に, 条件値が'%100%ジュース%'では正確な検索をすることができません。
リスト1 曖昧検索-'100%ジュース'を含むもの
-- 「'100%ジュース'を含むもの」
where xxx like '%100%ジュース%'
なにがまずいかというと,もしデータベース上に「100回回ってから飲むジュース」 というデータが入ってたら,このデータも検索対象になってしまうのです。 「'100%ジュース'を含むもの」という条件からは外れたデータです。 つまりは,ワイルドカード(%や_)も通常の文字として扱いたいこともあるのです。
SQLでは,文字として扱いたい'%'や'_'がある場合に,それらを条件値の中でエスケープします。 そのときエスケープ文字を明示的に指定します。
リスト2 エスケープ付き曖昧検索-ワイルドカード文字をエスケープ
-- 「'100%ジュース'を含むもの」
where xxx like '%100|%ジュース%' escape '|'
こうすることで,エスケープ文字でエスケープされた'%'や'_'はワイルドカードではなく, 通常の文字として扱われます。エスケープ文字をエスケープすることも可能です。
リスト3 エスケープ付き曖昧検索-エスケープ文字をエスケープ
-- 「'aaa|bbb'を含むもの」
where xxx like '%aaa||bbb%' escape '|'
この処理は,実業務において結構忘れられがちです。 それは,やらなくても支障のない場合も多いからです。 しかし,全く考慮せずに実装すると思わぬところでトラブることもあります。 商品の品番など複雑なコード体系では,'%'や'_'がコード上に存在することも あります。その場合に,「検索で余計なデータもたくさんでてきて,探せないんだけど」 とユーザに怒られてしまうこともあります。また,主に前方一致検索を想定している 検索で,先頭に'%'を入れられて中間一致検索になってしまい,インデックスが利用されずに SQLが全然帰ってこないという事態が発生する可能性もあります (前方一致にする理由としてインデックスを利用したいからって場合もあるのです)。
「第3回 ConditionBeanで色々な条件組み立て」で紹介したConditionBeanの PrefixSearchでは,実はエスケープ処理が施されません(簡易な前方一致)。 エスケープをする必要のないケースもたくさんあるので,大抵はこれでも よいのですが,上記のようなシビアな検索の場合にどうしたら良いのか, DBFluteの高機能曖昧検索「LikeSearch」をご紹介致します。
ConditionBeanのLikeSearch
ConditionBeanのLikeSearchの基本的な使い方を説明します。
query()メソッドの後,PrefixSearchではなくLikeSearchを選びます。 そのとき,第二引数が必須です。LikeSearchOptionというクラスを生成して, likePrefix()というメソッドを呼び出すことで前方一致になります。
リスト4 LikeSearchの基本的な使い方
/**
* 会員名称が'ス'で始まる会員を検索。
*
* @throws Exception
*/
public void test_ConditionBean_query_LikeSearch_likePrefix_Tx() throws Exception {
// ## Arrange ##
String prefix = "ス";
MemberCB cb = new MemberCB();
LikeSearchOption option = new LikeSearchOption().likePrefix();
cb.query().setMemberName_LikeSearch(prefix, option);
// これと同じ
// cb.query().setMemberName_PrefixSearch(prefix);
// ## Act ##
List<Member> memberList = memberBhv.selectList(cb);
// ## Assert ##
assertNotNull(memberList);
assertNotSame(0, memberList.size());
for (Member member : memberList) {
log.debug("memberName=" + member.getMemberName());
if (!member.getMemberName().startsWith(prefix)) {
fail();
}
}
}
リスト5 LikeSearchの基本的な使い方-SQL
select ...
from MEMBER dflocal
where dflocal.MEMBER_NAME like 'ス%'
likePrefix()の部分をlikeContain()やlikeSuffix()にすることで中間一致や後方一致に することも可能です。
リスト6 LikeSearchの一致の種別
new LikeSearchOption().likePrefix(); // 前方一致
new LikeSearchOption().likeContain(); // 中間一致
new LikeSearchOption().likeSuffix(); // 後方一致

