How to write NSFetchRequest with multiple sort descriptors? - ios

I have a Student Entity which has a dateOfBirth attribute.
I want to fetch all Students from the Student Entity in ascending order of their date fo birth but grouped by the decending order of the month.
for example: students which belong to the same Month are grouped together in ascending order but all the groups are in descending order.
Section 1: December 2019
-- student 1: DOB - 1 December 2019
-- student 2: DOB - 2 December 2019
-- student 3: DOB - 3 December 2019
Section 2: November 2019
-- student 1: DOB - 1 November 2019
-- student 2: DOB - 2 November 2019
-- student 3: DOB - 3 November 2019
Section 3: October 2019
-- student 1: DOB - 1 October 2019
-- student 2: DOB - 2 October 2019
-- student 3: DOB - 3 October 2019
Is it possible to write a NSFetchRequest to support this query? I have to use NSFetchResultsController to populate UITableView but I am unable to write a fetch request.
To group the students by their dateOfBirth I have an extra attribute which is a yearMonthString with dateFormat yyyyMM. I am using yearMonthString attribute to group students.
let studentFetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
studentFetchRequest.predicate = NSPredicate(format: "%K != nil AND %K != nil", #keyPath(Student.dateOfBirth), #keyPath(Student.yearMonthString))
let dateSortDescriptor = NSSortDescriptor(key: #keyPath(Student.dateOfBirth), ascending: false)
studentFetchRequest.sortDescriptors = [dateSortDescriptor]
let frc = NSFetchedResultsController<Student>(fetchRequest: studentFetchRequest,
managedObjectContext: managedContext,
sectionNameKeyPath: #keyPath(Student.yearMonthString),
cacheName: nil)
If I give dateSortDescriptor as ascending then all section also become ascending and if I give dateSortDescriptor as descending then students within the section becomes descending.
What is the correct way to write this fetch request? Is it possible to give different sort descriptor for the sections?

Use two sort descriptors, one to sort by the year/month (descending), then another to sort by date of birth (ascending):
let groupSortDescriptor = NSSortDescriptor(key: #keyPath(Student.yearMonthString), ascending: false)
let dateSortDescriptor = NSSortDescriptor(key: #keyPath(Student.dateOfBirth), ascending: true)
studentFetchRequest.sortDescriptors = [groupSortDescriptor, dateSortDescriptor]

Related

Nested data filter in CoreData?

Is it possible to filter the data in CoreData?
Using predicate I have sorted by name.
But I need to sort by date, make a selection of payments per month, for example.
There are two entities:
CategoriesEntity
name:String
TransactionsEntity:
sum: Double
date: Date
note: String
when filtering i get a list by category
let predicate = filterKey.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %#", filterKey)
fetchRequest = FetchRequest<T>(entity: T.entity(), sortDescriptors: [], predicate: predicate)
but how can I take transactions by date from the request if it is a nested entity in the entity category?
upd: categories and transactions are connected One-to-many

How do I form an NSSortDescriptor using a Boolean comparison

I'm fetching objects from a Core Data database for an iOS app that have multiple properties, and on my 3rd of the sort keys, I want to sort on Job Title, alphabetically ascending, but with the twist that "Chair" (i.e. the position of Department Chairman) sorts ahead of all other job titles (even those that are alphabetically ahead of it). The code I have below attempts to use the third sort descriptor to sort "Chair" above all other faculty positions using a Boolean, and then the 4th sort descriptor ranks all positions (i.e. sorting among all the job titles besides "Chair") alphabetically.
As is, I get an error on that third descriptor, having tried various permutations of the syntax that I can think of.
If I comment out that third descriptor, everything works & I get good results, other than the job title of Chairman falls in alphabetical order (so "Adjunct" then "Chair" then "Emeritus" and etc). What I want is -- after the first and second sorting keys have been applied -- for job titles to sort "Chair" first and then "Ajunct", "Emeritus", etc.
How should I be coding that third sort descriptor to make that happen?
I suspect that this question or this question may accomplish this, but the answers to both of those are in Objective C, and I'm not able to follow that enough to see how I'd convert either of those solutions to Swift.
func allProjects() -> [Projects] {
// fetches the array of Projects from Core Data & returns it.
do {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Projects")
// Order that I want to sort:
// 1 First by Month/Year (expressed as an Int32, 201803 = March 2018, for instance
// 2 then by Department (Astrology then Biology then Chemistry then Economics then etc)
// 3 then by departmentPosition = “Chair” — put the Chairman’s project(s) first
// 4 then by departmentPosition — everybody ELSE in alpha order: Adjunct > Emeritus > Lecturer > etc
// 5 then by Industry Partner in alphabetical order
let sortDescript1 = NSSortDescriptor(key: "yearMonthInt", ascending: false)
let sortDescript2 = NSSortDescriptor(key: "department", ascending: true)
let sortDescript3 = NSSortDescriptor(key: "{departmentPosition == ‘Chair’}", ascending: false). // Problem is here
let sortDescript4 = NSSortDescriptor(key: "departmentPosition", ascending: true)
let sortDescript5 = NSSortDescriptor(key: "industryPartner", ascending: true)
fetchRequest.sortDescriptors = [sortDescript1, sortDescript2, sortDescript3, sortDescript4, sortDescript5]
do {
let fetched = try myContext.fetch(fetchRequest) as! [Projects]
return fetched
}
} catch {
fatalError("Couldn't fetch [Projects]: \(error)")
}
}
EDIT 1: An idea that doesn't quite work
It was suggested in a comment that the comparison needed to be
let sortDescript3 = NSSortDescriptor(key: "departmentPosition", ascending: true, comparator: myComparator)
The problem is, that also generates an error (CoreData: error: exception handling request: <NSSQLFetchRequestContext: 0x7b3400037e90> , unsupported NSSortDescriptor (comparator blocks are not supported)), and searching for the text of that error turns up this question/answer, which states that you can't use comparator blocks with Core Data.

Combining 2 values into a NSPredicate

I'm trying to do a fetch request that checks for 2 things.
Here is my Data:
Person - entity
Statement - entity
The Person entity has a relationship to statements as To Many. The statement entity has an attribute called amountOwed. This is the property I want to check in the predicate.
EDIT
What I am trying to do is this.
Check all of my Persons entities for a name, lets say Bob.
Once I find the Bob entity I want to check all of his Statement entities for an attribute called amountOwed and see if it's greater or less then 0.
Check for a name in the Person Entity. When that name matches, use that entity.
Check if the amountOwed in a Statement entity is greater or less then 0.
This is what I have been trying to get to work.
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
fetchRequest.predicate = NSPredicate(format:"name == %# AND #statement.amountOwed >= 0", personName))
let sort = NSSortDescriptor(key: #keyPath(Person.name), ascending: true)
fetchRequest.sortDescriptors = [sort]
positiveFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.managedContext, sectionNameKeyPath: nil, cacheName: nil)
do {
try positiveFetchedResultsController.performFetch()
} catch let error as NSError{
print("Fetching error: \(error), \(error.userInfo)")
}
I am getting this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
I found the AND method here: using AND link
Edit
You should build your FRC to fetch Statement objects, not Person objects:
let fetchRequest: NSFetchRequest<Statement> = Statement.fetchRequest()
Assuming the statement relationship has an inverse (to-one) relationship named person, then you can use the following predicate to ensure you fetch only the statements with a positive amountOwed relating to a person with a given name:
fetchRequest.predicate = NSPredicate(format:"person.name == %# AND amountOwed >= 0", personName)
(and similarly for the negative amountOwed). Specify sort descriptors to get whatever sort order you wish. Your FRCs' fetchedObjects arrays will then contain Statement objects which you can use to populate the table view: the positive FRC for section 0 and negative FRC for section 1 (or vice versa).
NB. because you are fetching the Statement objects, it is possible to achieve what you want with just one FRC - but only if you are happy with the statements being sorted by amountOwed (ascending or descending). If you wish to do this, I can provide further detail.
Try this:
NSPredicate(format:"name == %# AND statement.#amountOwed >= 0", personName)
The AND has to be part of the string as the example you attached...

Predicate to retrieve the last N unique names from CoreData.

I have a core data entity that looks like this:
name: String
creationDate: Date
I would like to retrieve the last 5 unique names when ordered in descending order by creation date. What is the most efficient way to query this data from CoreData?
Both these solutions are efficient depending on the situation.
If you do not have memory constraints (i.e. not too many records) you could fetch all sorted by date, use KVC to filter and take the top n records.
(allItems as NSArray).valueForKeyPath("#distinctUnionOfSets.name")
If you like to fetch this directly from the store, you have to drop back to NSDictionaryResultType for your fetch request. Something like
let request = NSFetchRequest(entityName:"Item")
request.resultType = NSDictionaryResultType
request.returnsDistinctResults = true
let entity = NSEntityDescription(name: "Item", inManagedObjectContext: context)
let description = entity.propertiesByName["name"]
request.propertiesToFetch = [description!]
request.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
request.fetchLimit = 5

Does SQLite create a numeric index that I can sort/fetch on?

I'm working on my first CoreData application and I'd like to sort my data based on how it was inserted into database.
My question is do I need to create an 'id' attribute for my entity or is there is a computed index that I can sort on?
There is no automatic index created. You should add an attribute of type NSDate and you can sort by that.
One convenient place to implement this is in the NSManagedObject subclass.
public override func awakeFromInsert() {
createdAt = NSDate()
}
And to sort:
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "createdAt" ascending: true)]

Resources