NSPredicate on multiple to-many relationships in Core Data - ios

I have an NSPredicate which includes multiple aggregate filters, which is throwing an exception.
I have the following core data model:
I want to pick those ApparelItems for which any of the colours has an rgb of 13576743, and for which all of the picks have a pickTime earlier than a given NSDate.
My code to create the predicate is:
let request = NSFetchRequest(entityName: "ApparelItem")
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "ANY colours.rgb = 13576743"))
// find the NSDate representing midnight x days ago
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: [])
{
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "(ALL picks.pickTime < %#)", midnightXDaysAgo))
}
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
let searchData = try? objectContext.executeFetchRequest(request)
I get the following exception:
Exception name=NSInvalidArgumentException, reason=Unsupported predicate (ANY colours.rgb == 13576743) AND ALL picks.pickTime < CAST(479347200.000000, "NSDate")
I have tried:
Each individual predicate works fine. Ie ANY colours.rgb = ... works, also ALL picks.pickTime < ... works. They just don't work when combined into the same query.
Combining the two using into a single query, linked with AND, rather than using NSCompoundPredicate. Result is the same.
Is it possible that core data simply doesn't support filtering on more than one to-many relationship? That would seem odd. In which case how should I do the above?

Probably could try SUBQUERY() for NSPredicate.
The code below I came out from some guess and not very sure if it works or not. Usually it takes me trial and error many times for a to-many query clause with NSPredicate.
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "SUBQUERY(colours, $colour, ANY $colour.rgb = 13576743).#count > 0"))
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: []) {
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "SUBQUERY(picks, $pick, ALL $pick.pickTime < %#).#count > 0", midnightXDaysAgo))
}

Colors and picks are two different entities, so you should be able to filter by both without using SUBQUERY. One predicate or a compound predicate should both be fine.
From the error message it seems that perhaps something is wrong with the date. Please check you have the expected data type in your managed object subclasses. If you used "primitive values" when creating the subclasses, you will need NSTimeInterval rather than NSDate.

Related

NSPredicate: search for an array of values in relation, NOT

I'm a bit lost, and I hope you can help me.
I have two arrays
let stars = ["Paul", "Ringo"]
let visitors = ["bob", "mary"]
Then I have Core Data entities Rockstar and Person. And a one-to-many relation fans between the two.
Now I want to find a couple of specific Rockstars, and make sure that they don't have visitors as fans.
I try to do that with a compound predicate, roughly like this:
let starsPredicate = NSPredicate(format: "id IN %#", stars)
let fansPredicate = NSPredicate(format: "NOT (fans.personid CONTAINS %#)", visitors)
and finally
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [starsPredicate, fansPredicate])
I'm afraid this results in two questions:
What is the correct syntax for the fansPredicate? It works fine with one value, but it crashes on an array
Is this possible with a compound predicate at all? I think if the Person entity is empty, I get zero records from the compoundPredicate.
This can be achieved with a “SUBQUERY”:
let fansPredicate = NSPredicate(format: "SUBQUERY(fans, $f, $f.personid IN %#).#count = 0",
visitors)
The predicate is true for all Rockstar objects which have no related Person object whose personId is in the given list.

Can't Query Single CloudKit Record by recordName UUID

I have a CloudKit app where records are stored locally in CoreData. When creating the CoreData record, a UUID is generated and populated to the system recordName field. The app creates and saves records in CoreData then uploads the records to CloudKit. This works fine.
However, I am now coding to modify a record and am not able to fetch a record by the recordName UUID. I have not been successful setting a predicate to search for only that record. I can retrieve all records with TRUEPREDICATE and I can also retrieve a single record from a field I created in the CloudKit record type. I created a field called myRecordName where I store the same UUID as the CloudKit recordName. A query using the myRecordName works fine.
I have shown three methods below for the predicates. If p1 and p2 are not both commented out the app crashes on running. I assume I am missing something really simple here. Any guidance would be appreciated. Xcode 10.2.1, iOS 12.2, testing on a real device.
If either p1 or p2 or both are not commented out, the console shows:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CKReference rangeOfString:]:
unrecognized selector sent to instance 0x280878520'
func queryDatabase() {
let recordZone = CKRecordZone(zoneName: "PrimaryZone")
let recName = "0E763775-5AD3-4A50-BFB8-AC310180E8A2"
let recID = CKRecord.ID(recordName: recName, zoneID: recordZone.zoneID)
let searchRecord = CKRecord(recordType: "Patient", recordID: recID)
let p1 = NSPredicate(format: "%K == %#", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
//let predicate = NSPredicate(format: "TRUEPREDICATE")
let p3 = NSPredicate(format: "myRecordName = %#", recName)
let query = CKQuery(recordType: "Patient", predicate: p3)
let sortDescriptor = NSSortDescriptor(key: "lastNameText", ascending: true)
query.sortDescriptors = [sortDescriptor]
privateDatabase.perform(query, inZoneWith: recordZone.zoneID) { (results, error) in
if error != nil {
print("error querying database\(String(describing: error?.localizedDescription))")
} else {
DVC.ckRecords = results!
//print(results!)
print(DVC.ckRecords.first!)
}//if error else
}//perform query block
}//queryDatabase
I've never used CloudKit, so maybe I'm wrong, but…
The two problem lines of code, p1= and p2=, attempt to create a NSPredicate using a format string. Each format string contains two var arg substitutions, aka placeholders: %K and %#. So for this to work, that format string argument must be followed by two more arguments: first a key path, which should be a string (%K), and second a object for its value (%#). For your purposes here, the key path is probably just an attribute name.
But you have only provided one more argument, a CKRecord.Reference object, which the system tries to parse for the first (attribute name, string) argument. The error you are getting is typical of what happens when the system tries to parse an object such as CKRecord.Reference when it was expecting a string. Swift may be quite type safe but old-fashioned NSPredicate var args functions are not :)
To fix this problem (and move on to the next one), you should provide the key path (argument name) argument, something like this:
let p1 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
Since I don't understand exactly what you are doing, you may need to tweak those two lines a bit. But the point is that, given your format string, you need three arguments to NSPredicate(format:), and the middle one needs to be a string representing a key path or attribute name.

NSPredicate for Nested Relationships (many to many) iOS

I've been struggling for several hours and thought I'd reach out to SO. In my app's data model, I have an entity, SubCategory, which has many CreatedTask, and each CreatedTask can have many User:
Say I have an User entity variable, called userObject.
I want to fetch all SubCategories, where tasks.assignees has userObject. How do I do this?
Here is what I've tried so far:
let userFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SubCategory")
let predicate = NSPredicate(format: "%# IN tasks.assignees", userObject)
fetchedResultsController.fetchRequest.predicate = predicate
However I am generating a nasty error:
unimplemented SQL generation for predicate.
Any help would be greatly appreciated.
Full Error Output:
Thanks to yan's help I was able to figure it out, just had to place the user object into an array.
let users: [User] = [userObject]
let predicate = NSPredicate(format: "SUBQUERY(tasks, $m, ANY $m.assignees IN %#).#count > 0", users)

NSPredicate Maximum time with swift2

I am using coredata and I want to find maximum time in coredata.
How do I write?
let predicate = NSPredicate(format: "contentDate = '???' ", "")
fetchRequest.predicate = predicate
As Wain said, to find maximum value you rather have to define SortDescriptor then Predicate:
let sortByTimeDescriptor = NSSortDescriptor(key: "time", ascending: false)
fetchRequest.sortDescriptors = [sortByTimeDescriptor]
You are interested just in the one (maximum) value so it's also good practice to set fetch limit for performance reason:
fetchRequest.fetchLimit = 1
Then just execute request and get first object from results array:
let results = managedObjectContext.executeFetchRequest(fetchRequest)
let maxTimeEntity = results.first
Notice that executeFetchRequest can throw and error and you have to handle it somehow.
To find a maximum it makes more sense to sort and ask for the first item than to try to create a predicate.
You can also look at an expression using #max to get the value back on its own if the container object isn't important.

Using "timeIntervalSinceNow" in a predicate in SWIFT

I am trying to query for records that are created in the last 1 hour using Cloudkit (in SWIFT)
I have tried:
let predicate = NSPredicate(predicateWithFormat : "timeIntervalSinceNow %# < %f", "creationTime", -3600)
without success. The error message refers to a parsing error.
Anyone has a clue what would be the right format?
n.b creationTime is the name of the field in the Table
Your predicate isn't valid. Take a look at Apple's guide for creating NSPredicates.
You should use a key path on the left hand side of the expression. I assume that you have an array of objects, and these objects have a property named creationTime. Then the predicate should look like this:
NSPredicate(format: "creationTime.timeIntervalSinceNow > %d", -3600)
Note that I have used "greater than" > operator instead of < in the predicate you posted. That's because a date from the past will return a negative value from timeIntervalSinceNow method, as documentation states.
EDIT:
Seems you can't use key paths in CloudKit predicates. But I think you can create a reference date (an hour ago) and use it for comparison predicate:
let now = NSDate()
if let anHourAgo = NSCalendar.currentCalendar().dateByAddingUnit(
.HourCalendarUnit,
value: -1,
toDate: now,
options: NSCalendarOptions(0)) {
let predicate = NSPredicate(format: "creationDate > %#", anHourAgo)
let filtered = records.filteredArrayUsingPredicate(predicate!)
}

Resources