Search code examples
swiftcore-datanspredicate

Unexpected result from CoreData compound fetch with date range


I have a simple data model with visitors (fName and lName) and visits (date, comment) in a 1-m relation. I'm trying to fetch all visitors for a particular day. In order to achieve this, I need to create a predicate to look for all entities having visits between the start of that day and its end. I have tried:

...
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "visitor")
let filter = "ANY visits.date >= %@ AND ANY visits.date < %@"  // visits is the visitors relation to visits
let startTime = calendar.startOfDay(for: thisDay) as NSDate
let endTime = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: thisDay)! as NSDate
fetchRequest.predicate = NSPredicate(format: filter, startTime, endTime)

The result are all visitors with visit dates at and after this day. Hence the second part of the compound predicate has no effect! I've also tried to combine the two conditions with the NSCompoundPredicate, but the result is the same. I've experimented with other than date types with the same compound predicate and these all worked well. So despite extensive searches in the Internet, I have no clue how to solve this simple query with predicates.

Any suggestions are most welcome!


Solution

  • Your predicate fetches all visitors which are related to (at least one) visit with visits.date >= startDate and to (at least one) visit with visits.date < endDate. The problem is that those two related objects need not be the same for the predicate to return true.

    What you need is a “subquery”: From the (related) NSExpression documentation:

    This method creates a sub-expression, evaluation of which returns a subset of a collection of objects. It allows you to create sophisticated queries across relationships, such as a search for multiple correlated values on the destination object of a relationship.

    ...

    The string format for a subquery expression is:

    SUBQUERY(collection_expression, variable_expression, predicate);
    

    where expression is a predicate expression that evaluates to a collection, variableExpression is an expression which will be used to contain each individual element of collection, and predicate is the predicate used to determine whether the element belongs in the result collection.

    In your case it should be (untested):

    NSPredicate(format: "SUBQUERY(visits, $v, $v.date >= %@ and $v.date < $@).@count > 0",
                startTime, endTime)
    

    This fetches all visitors which are related to at least one visit whose date falls into the specified range.