Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

iOSのNsPredicateを使ってスペース区切りの文字列の検索条件を指定する&RealmでDBから読み込むメモ

こんにちは。自分の勉強メモです。
間違っていたらお声がけ下さると嬉しいです(●´ω`●)

前提

環境はXcode9.2、Swift4.0を使用しています。
Realmを使ったDBの処理を書いている最中です。

やりたいこと

よくあるキーワード検索がやりたいです。
・スペース区切りの文字列を入力された時に、スペースで文字列を分割する。
・対象のフィールドに分割した文字列がそれぞれ含まれているものを抽出する。

このように検索条件を入力したとすると
f:id:sakura_bird1:20180123224904p:plain


SQL文は次のようになります。

SELECT  "foods".* FROM "foods" WHERE (("foods"."name" LIKE '%たま%' OR "foods"."search_word" LIKE '%たま%') AND ("foods"."name" LIKE '%豆%' OR "foods"."search_word" LIKE '%豆%'))

この例では"foods"というテーブルから"name"または"search_word"というフィールドに
入力した文字列があった時にデータが抽出されます。

検索条件を指定するには

NsPredicateクラスを使っての検索条件を指定するのが定石のようです。
RealmでもNsPredicateがサポートされておりNSPredicate Cheatsheetという資料が用意されています。
NSPredicate Cheatsheet

前処理

検索する文字列をスペースで分割して配列に入れておきます。
有効な文字列が入っている前提です。

    let words = " たま 豆"
    // 全角スペースを半角スペースに置き換え余計なスペースはトリムする
    let newWords = words.replacingOccurrences(of: " ", with: " ").trimmingCharacters(in: .whitespacesAndNewlines)
    // componentsメソッドで指定の区切り文字で配列に分割する
    let wordsArray = newWords.components(separatedBy: " ")
    print(wordsArray) // ["たま", "豆"]

↑2018/03/18追記 トリム以外にも大文字小文字の区別も無くしたいという要望が多いと思うので、lowercased()などの関数を利用してもいいですね。

NsPredicateに条件を指定する

Swiftでは「let predicate = NSPredicate(format: "search_word CONTAINS %@", "たま")」
のようにformatに条件文を入れていきます。
NSPredicate - Foundation | Apple Developer Documentation

私の例ですと、SQLのLIKE、つまり文字列の部分一致に対応するオペレーターは
「CONTAINS」を使います。

また、比較したい文字列が複数あるかもしれない前提なので
NSPredicateオブジェクトを配列に格納してから「NSCompoundPredicate」クラスを利用してそれぞれの条件を結合します。
AND条件なので「andPredicateWithSubpredicates」を指定します。
比較したい文字列が1つしかなくてもこのコードで問題ありません。

    var predicates: [NSPredicate] = []

    for word in wordsArray {
      predicates.append(NSPredicate(format: "search_word CONTAINS %@ OR name CONTAINS %@", word, word))
    }

    print(predicates)// [search_word CONTAINS "たま" OR name CONTAINS "たま", search_word CONTAINS "豆" OR name CONTAINS "豆"]

    let compoundedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)

    print(compoundedPredicate)//(search_word CONTAINS "たま" OR name CONTAINS "たま") AND (search_word CONTAINS "豆" OR name CONTAINS "豆")

Realmで検索する

filterメソッドに先程のNSCompoundPredicateオブジェクトをセットすればOKです。
公式ドキュメントはこちらです。
https://realm.io/docs/swift/latest/#queries

let result = realm.objects(Foods.self).filter(compoundedPredicate)

以上です。