Search code examples
swiftcore-datanspredicate

Compare two columns using the contains predicate


I have a Core Data entity with two columns: artistName, of type String, and a relationship with another entity which has a column name, also of type String.

I can create a predicate which compares these two columns like so:

NSPredicate(format: "artistName == artist.name")

So for a row with artistName = Beyoncé and artist.name = Beyoncé, this predicate returns true.

But, if I change it to use contains, it doesn't work:

NSPredicate(format: "artistName contains artist.name")

The code above results in this error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (artistName CONTAINS artist.name) (LHS and RHS both keypaths)'

I want to use contains to create a predicate that would return true for a row with artistName = Beyoncé feat. Jay-Z and artist.name = Beyoncé, for example.

How can a compare two columns using the contains predicate?


Solution

  • There is a lot written on the use of predicates. While it is a few years ago now... a very good article from NSHipster.

    Here are some links to the Apple Documentation:

    The answer to your question lies in the error message LHS and RHS both keypaths.

    You have written a simple predicate as:

    NSPredicate(format: KEYPATH comparator KEYPATH)

    In your case, you should write a simple predicate as:

    NSPredicate(format: KEYPATH comparator VALUE)


    UPDATE

    I was attempting to keep my answer short, to encourage the OP to develop an understanding of predicate syntax... however perhaps more explanation is required...

    You have a Core Data entity that I'm guessing is named Artist with the following:

    • attribute of type String and name artistName;
    • relationship of type Artist (guessing?) and name name.

    You want to check whether both artistName and name contain the String "Beyoncé".

    How can you do that with one comparison??? In my humble opinion, impossible.

    You need two comparisons:

    • check whether the value for attribute artistName contains "Beyoncé";
    • check whether the value for relationship name contains "Beyoncé".

    So how do you write a "compare" predicate with two comparisons?

    There are a number of ways to do this.

    In the longer term, I'd encourage you to investigate the power of NSCompoundPredicate as I am certain it will serve you well in the future.

    In the meantime, you can write a "two comparison" predicate using the following syntax...

    let predicate = NSPredicate(format: "(artistName contains 'Beyoncé') AND (name.artistName contains 'Beyoncé')")
    

    or dynamically / generically...

    let keypath1: String = "artistName"
    let keypath2: String = "name.artistName"
    let value: String = "Beyoncé"
    
    let predicate = NSPredicate(format: "(%K contains %@) AND (%K contains %@)", argumentArray: [keypath1, value, keypath2, value])
    

    NOTES:

    • you do have to traverse the relationship; and
    • you may need to write as contains[cd] to make the comparison case-insensitive and diacritic-insensitive, thereby dealing with characters such as "é".

    You may see now that in your question, the first == comparator works only because you are literally comparing two implied string values. The second contains does not work because predicate syntax rules expect the second argument to be a value (LHS a keypath and RHS a value). Swift is reading the second contains as "attribute artistName contains the entity associated with the relationship name"

    Diego Freniche built a playground to demonstrate the use of predicates... maybe worth looking at for practical examples? (https://github.com/dfreniche/NSPredicate-Swift)