问题
Given a core data entity setup as follows
- Entity A
- Bs -> B (many to many)
- Cs -> C (many to many)
- children -> Child (many to many)
- Entity B
- children -> Child (many to many)
- Entity C
- children -> Child (many to many)
- Child
- date
I've wanted to query for Entity A's where any of the children (in Entity A, B, or C) have dates that pass some query like being greater than a specified date.
Given the nested relationships some nested subqueries were required, so for checking all the Bs children from A with something like this
predicateString += "(SUBQUERY(Bs, $b, SUBQUERY($b.children, $child, $child.date >= %@).@count > 0).@count > 0)"
predicateVars = [testDate! as NSDate]
let predicate = NSPredicate(format: predicateString, argumentArray: predicateVars)
Repeating the same form of query for Cs and then just doing a single SUBQUERY for A's direct children.
This all works fine after some fine tuning of the predicate form (ensuring all the required counts were added).
The small problem arose when I wanted to now perform a similar search but instead of requiring the children's date to be greater than a test date I wanted to just find all A's who had any children in either A.children, Bs.children, Cs.children.
The nested SUBQUERIES caused problems, at first I thought it'd be fine to do a single level SUBQUERY and then check the count of the children relationship within it, so something like this
predicateString += " SUBQUERY(Bs, $b, $b.children.@count > 0).@count > 0"
Which is accepted as a valid predicate but when its evaluated you hit an exception
[error] error: exception handling request: <NSSQLFetchRequestContext: 0x60000018ec70> , Keypath containing KVC aggregate where there shouldn't be one; failed to handle $b.children.@count with userInfo of (null)
A quick search of stack overflow came up with the same error in this question
Keypath error with Core Data SUBQUERY and NSFetchedResultsController
Which seemed to suggest that you cannot do the @count within a SUBQUERY and therefore have to do another SUBQUERY over the children to get at the same result. Which would be the same form as my first example above, albeit I need a condition in my SUBQUERY that is always true for each element of the children. My first thought was hopefully that I could just use TRUE like this
predicateString += "(SUBQUERY(Bs, $b, SUBQUERY($b.children, $child, TRUE).@count > 0).@count > 0)"
It feels quite wasteful, but would do what I wanted. Unfortunately it fails to parse as a valid predicate.
Beyond this I have managed to succeed by making queries that should always be true, so comparing the date against 1970 as follows
let date1970 = Date.init(timeIntervalSince1970: 0)
predicateString += "(SUBQUERY(Bs, $b, SUBQUERY($b.children, $child, $child.date >= %@).@count > 0).@count > 0)"
predicateVars = [date1970! as NSDate]
This works, but feels like doing a bit more work than I'd prefer to just get a count. A simpler example that works is also just testing each child against nil, so
predicateString += "(SUBQUERY(Bs, $b, SUBQUERY($b.children, $child, $child != nil).@count > 0).@count > 0)"
predicateVars = [testDate! as NSDate]
This seems much better but I just wondered if there was a better option. I honestly don't understand why the TRUE condition alone isn't a valid predicate.. yes its wasteful, but it seems in this case we must do an effectively wasteful subquery to get the count we want.
Anyone got any better ideas?
来源:https://stackoverflow.com/questions/51753032/nspredicate-to-detect-non-empty-relationships-within-a-subquery