分かっていると当たり前の話なのですが、CoreData で SQL ライクに NOT LIKE を書こうとした時に、「ん?」となったので、ちょっと覚え書き。
もし、どなたかのお役に立ちましたら。
ちなみに、使用言語は Swift ですが、Objective-C でもほぼ変わらない筈です。
読み込み中です。少々お待ち下さい
まえおき
CoreData の NSPredicate でも、当然ながら「LIKE」は使えるのですが、書き方が SQL の LIKE とは微妙に異なります。
まず、ワイルドカードが「%」「_」ではなく「*(0 文字以上一致)」「?(1 文字以上一致)」という違いがあります。
また、LIKE の他に「BEGINSWITH」「CONTAINS」「ENDSWITH」が用意されており、それぞれ SQL の LIKE で良く使われる「pattern%」「%pattern%」「%pattern」に相当すると考えて良いでしょう。
ですので、SQL 的な感覚を引き摺っていると少々気持ち悪く感じられるのですが、CoreData では「LIKE」は素直に文字列の完全一致に使用し、単純な部分一致に関しては、パターンに含まれる「*」や「?」のエスケープをいちいち考慮しなくてはならない「LIKE」よりも、「BEGINSWITH」「CONTAINS」「ENDSWITH」を使った方が楽ができそうです。
余談ですが、さらに複雑なパターンマッチには、正規表現を扱える「MATCHES」を使うことになるでしょう。
さて、もう少し具体的に、コードで見てみましょう。
例えば「title」という Attribute(SQL でいう COLUMN)を持つ「Event」という Entity(SQL でいう TABLE)が存在するとします。
SQL ならば「SELECT * FROM EVENT WHERE TITLE LIKE '%hoge%'」と書くような場合、CoreData では以下のようなコードになります。
// これは単なるサンプルです。context は NSManagedObjectContext であれば何でも構いません
let context = self.fetchedResultsController.managedObjectContext
let pattern = "hoge"
let request = NSFetchRequest(entityName: "Event")
request.predicate = NSPredicate(format: "title CONTAINS %@", pattern)
do {
if let results = try context.executeFetchRequest(request) as? [NSManagedObject] {
for result in results {
// 何らかの処理を行います。ここではサンプルとして Attribute 名と値を列挙しています
print("{")
for attributeName in result.entity.attributesByName {
print(" \(attributeName.0) = \(result.valueForKey(attributeName.0))")
}
print("}")
}
}
} catch {
// Error handling
}
NOT LIKE を書く
以上の前提を踏まえて、ようやく本題の NOT LIKE です。
SQL と同じ感覚で、次のように書くとエラーが発生します。
request.predicate = NSPredicate(format: "title NOT LIKE %@", pattern)
// 以下のようなエラーが発生します
// 'NSInvalidArgumentException', reason: 'Unable to parse the format string "title NOT LIKE %@"'
CoreData でも NOT は使えるのですが、置く位置が異なります。以下のように書きます。
request.predicate = NSPredicate(format: "NOT title LIKE %@", pattern)
上述したように、SQL でいうところの「LIKE '%pattern%'」は「CONTAINS」なので、実際は次のように書くことになるでしょう。
request.predicate = NSPredicate(format: "NOT title CONTAINS %@", pattern)
おわりに
単語としては SQL と同じなのに、書き方や意味が異なるケースがちょいちょいあるのが紛らわしいです。
どうせ中身は SQLite なんだから(いや、SQLite とは限らないけど)、下手に似たような書き方をするくらいなら、いっそのことそのままでいいじゃないかと思うのですが、きっとなんか色々あるんでしょう。
まぁ、「NOT (attribute1 LIKE %@ OR attribute2 LIKE %@)」みたいに書けるので、より理に適っていると言われれば、そんな気もしますけれども。
NSPredicate のフォーマットについてより詳しくは、本家のこちらのドキュメント等を参照してください。