Core Data complex query - core-data

I have model that have field date. I need to fetch all count of records for all day of year or if no record for this day presented fetch as 0. Now i fetch all record for year and make it by hands. Can I do it using core data queries?

Typically in Core Data it is much faster to do all the fetching you will need to do up front rather than breaking it up into multiple fetches.
I tried to count records by day of the year in two ways:
Fetch all the records for a year. Find distinct dates for those records. Loop over the dates and count the records.
Loop over each day in the year. Perform a count-only fetch for the records with that date.
With a small database (48 records), the first approach was about 90 times faster. I imagine the performance of the first approach would get worse as more records are added and the second approach would stay about the same.
Here is the code:
- (void)doFullLoadTestWithYear:(int)year {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Record" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setDay:1];
[comps setMonth:1];
[comps setYear:year];
NSDate *start = [gregorian dateFromComponents:comps];
[comps setDay:31];
[comps setMonth:12];
NSDate *end = [gregorian dateFromComponents:comps];
NSPredicate *yearPred = [NSPredicate predicateWithFormat:#"date >= %# && date <= %#",start,end];
[fetchRequest setPredicate:yearPred];
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if (error) {
NSLog(#"Error during fetch: %#",[error localizedDescription]);
abort();
}
int dateCount = 0, recordCount = 0;
NSArray *dates = [array valueForKeyPath:#"#distinctUnionOfObjects.date"];
for (NSDate *date in dates) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"date == %#",date];
NSArray *records = [array filteredArrayUsingPredicate:pred];
dateCount++;
recordCount += [records count];
NSLog(#"%d record(s) with date %#",[records count],date);
}
NSLog(#"Record count for year is %d",recordCount);
NSLog(#"Distinct dates count is %d",dateCount);
}
- (void)doSeparateTestWithYear:(int)year {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Record" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setDay:1];
[comps setMonth:1];
[comps setYear:year];
NSDate *start = [gregorian dateFromComponents:comps];
[comps setYear:year+1];
NSDate *end = [gregorian dateFromComponents:comps];
NSDateComponents *oneDay = [[[NSDateComponents alloc] init] autorelease];
[oneDay setDay:1];
int dateCount = 0, recordCount = 0;
NSDate *date = start;
while (![date isEqual:end]) {
NSPredicate *dayPred = [NSPredicate predicateWithFormat:#"date == %#",date];
[fetchRequest setPredicate:dayPred];
NSError *error = nil;
int count = [self.managedObjectContext countForFetchRequest:fetchRequest error:&error];
if (error) {
NSLog(#"Error during fetch: %#",[error localizedDescription]);
abort();
}
if (count > 0) {
NSLog(#"%d record(s) with date %#",count,date);
dateCount++;
recordCount += count;
}
date = [gregorian dateByAddingComponents:oneDay toDate:date options:0];
}
NSLog(#"Record count for year is %d",recordCount);
NSLog(#"Distinct dates count is %d",dateCount);
[fetchRequest release];
}

Related

Fetching Sorted Array based on NSDate from Core Data Entity in iOS9 using Objective C

I am using core data for storing alarm details (AlarmDate is stored as Date type), while fetching all alarms i have used NSSortDescripotor as
NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Alarm"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"AlarmDate"
ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *arrayForAllAlarms = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
for above it is returning the alarms in sorted order, but i need it should also consider the AM and PM while sorting
Example:
if alarms date are added like:
11.00 pm,
01.30 am and
10.00 pm
it should return:
01.30 am
10.00 pm and
11.00 pm
Thank you!
Find Solution, Written custom sorting for existing fetched array as following:
NSArray *arrayForAllAlarmsSorted = [arrayForAllAlarms sortedArrayUsingComparator:
^(Alarm *alarmObject1, Alarm *alarmObject2) {
NSDate *dateForAlarm1 = [NSDate date:alarmObject1.alarmDateTime withFormat:#"hh:mm a"];
NSDate *dateForAlarm2 = [NSDate date:alarmObject2.alarmDateTime withFormat:#"hh:mm a"];
return [dateForAlarm1 compare:dateForAlarm2];
}];
And the NSDate Category function written as:
+(NSDate *)date:(NSDate *)date withFormat:(NSString *)format {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setTimeZone:[NSTimeZone localTimeZone]];
dateFormatter.dateFormat = format;
NSDate *newDate = [dateFormatter dateFromString:[dateFormatter stringFromDate:date]];
return newDate;
}

coredata subquery with multiple conditions / relations

I have the following model:
how can I get all the PT objects where groupId == '2'?
I have tried several ways, but without success.
With this query, I get one result only:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Status" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"group.groupId == %#", [NSNumber numberWithInt:2]];
NSError *error = nil;
NSArray *resultSet = [self.managedObjectContext executeFetchRequest:request error:&error];
[request release];
NSLog(#"array count: %lu", (unsigned long)[resultSet count]);
NSPredicate *thePredicate = [NSPredicate predicateWithFormat:#"hasBeenDeleted == %# AND (ANY hasStatus IN %#)",
[NSNumber numberWithBool:NO],
[NSSet setWithArray:resultSet]];
[self.ptListViewController refreshDataWithPredicate:thePredicate];
You could start with a template "GROUP_BY_GROUP_ID" and add it to the managedObjectModel ...
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"GroupdId2" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSFetchRequest *tmpTemplate;
NSEntityDescription *tmpEntity;
NSPredicate *tmpPredicate;
// 1. GROUP_BY_GROUP_ID
tmpEntity = [[_managedObjectModel entitiesByName] objectForKey:#"Group"];
tmpTemplate = [[NSFetchRequest alloc] init];
[tmpTemplate setEntity:tmpEntity];
tmpPredicate = [NSPredicate predicateWithFormat:#"(groupId == $value)"];
[tmpTemplate setPredicate:tmpPredicate];
[_managedObjectModel setFetchRequestTemplate:tmpTemplate forName:#"GROUP_BY_GROUP_ID"];
return _managedObjectModel;
}
Here is the appropriate fetch method:
- (NSArray *)fetchGroupByGroupId:(NSNumber *)value
{
NSError *error = nil;
NSManagedObjectModel *model = _managedObjectModel;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:value, #"value", nil];
NSFetchRequest *fetchRequest = [model fetchRequestFromTemplateWithName:#"GROUP_BY_GROUP_ID" substitutionVariables:substitutionDictionary];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
return results;
}
Now you can start fetching PT entities like this (Xcode lets you generate the NSManagedObject subclasses for all entities):
NSArray *groups=[self fetchGroupByGroupId:#(2)];
NSMutableSet *resultPT=[[NSMutableSet alloc] init];
for (Group *group in groups) {
for (Status *status in group.hasStatus) {
for (PTStatus *ptstatus in status.ptStatus) {
if (ptstatus.targetPT!=nil) {
[resultPT addObject:ptstatus.targetPT];
}
if (ptstatus.pt!=nil) {
[resultPT addObject:ptstatus.pt];
}
}
}
}
In resultPT now you will find all different PT entities "with" groupId == '2'.
You find a XCode 6.1 project here: XCode 6.1 project on Dropbox.
Hope it helps.
I found it!
I had a bug when relating the Status with the Group.
Correct code for achieving:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"PTStatus" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"ANY status.group.groupId == %#", [NSNumber numberWithInt:2]];
NSError *error = nil;
NSArray *resultSet = [self.managedObjectContext executeFetchRequest:request error:&error];
[request release];
NSLog(#"array count: %lu", (unsigned long)[resultSet count]);
NSPredicate *thePredicate = [NSPredicate predicateWithFormat:#"hasBeenDeleted == %# AND (ANY hasStatus IN %#)",
[NSNumber numberWithBool:NO],
[NSSet setWithArray:resultSet]];
[self.ptListViewController refreshDataWithPredicate:thePredicate];
and [self.ptListViewController refreshDataWithPredicate:thePredicate]; has
- (void)refreshDataWithPredicate:(NSPredicate *)predicate{
self.fetchedResultsController = nil;
[NSFetchedResultsController deleteCacheWithName:#"PTs_Cache"];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"PT" inManagedObjectContext:self.managedObjectContext];
request.sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"requestDate" ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"number" ascending:YES],
nil];
request.predicate = predicate;
request.fetchBatchSize = 40;
NSString *sectionNameKeyPathString = #"dateSection_transient";
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:sectionNameKeyPathString
cacheName:#"PTs_Cache"];
[request release];
self.fetchedResultsController = frc;
[frc release];
[self.theTable reloadData];
}

Core Data: Not saving

I'm having trouble saving to one variable letsMeet.startTimeLabel. Right after selecting NSLog shows the correct Value, however, after I save to another variable (letsMeet.endTimeLabel), letsMeet.startTimeLabel changes to (NULL). Below is the code:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
letsMeet = (LetsMeet *) [NSEntityDescription insertNewObjectForEntityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext];
switch (actionSheet.tag)
{
case 1:
{
if (buttonIndex == 0)
{
UIDatePicker *startDatePicker = (UIDatePicker *)[actionSheet viewWithTag:kDatePickerTag1];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd"];
NSDate *selectedDate = [startDatePicker date];
NSDateFormatter *dayFormatter = [[NSDateFormatter alloc] init];
[dayFormatter setDateFormat:#"EEEE"];
NSDate *selectedDay= [startDatePicker date];
NSDateFormatter *monthFormatter = [[NSDateFormatter alloc] init];
[monthFormatter setDateFormat:#"MMMM"];
NSDate *selectedMonth = [startDatePicker date];
NSString *date = [[NSString alloc] initWithFormat:#"%#", [dateFormatter stringFromDate:selectedDate]];
DateLabel.text = date;
[letsMeet setDateLabel:date];
NSString *month = [[NSString alloc] initWithFormat:#"%#", [dayFormatter stringFromDate:selectedMonth]];
MonthLabel.text = month;
[letsMeet setMonthLabel:month];
NSString *day = [[NSString alloc] initWithFormat:#"%#", [monthFormatter stringFromDate:selectedDay]];
DayLabel.text = day;
[letsMeet setDateLabel:day];
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
[timeFormatter setDateFormat: #"h:mm a"];
NSDate *selectedStartTime = [startDatePicker date];
NSString *startTime = [[NSString alloc] initWithFormat:#"%#", [timeFormatter stringFromDate:selectedStartTime]];
StartTimeLabel.text = startTime;
[letsMeet setStartTimeLabel:startTime];
NSError *error = nil;
if (![managedObjectContext save:&error]){
NSLog(#"Error Saving");
}
}
NSLog (#"This is the StartTime after selecting %#", letsMeet.startTimeLabel);
}
break;
case 2:
{
if (buttonIndex == 0)
{
UIDatePicker *endTimePicker = (UIDatePicker *)[actionSheet viewWithTag:kDatePickerTag2];
NSDateFormatter *endTimeFormatter = [[NSDateFormatter alloc] init];
[endTimeFormatter setDateFormat: #"h:mm a"];
NSDate *endSelectedTime = [endTimePicker date];
NSString *endTime = [[NSString alloc] initWithFormat:#"%#", [endTimeFormatter stringFromDate:endSelectedTime]];
EndTimeLabel.text = endTime;
[letsMeet setEndTimeLabel:endTime];
NSLog (#"This is the EndTime %#", letsMeet.endTimeLabel);
NSLog (#"This is the StartTime after selecting BOTH %#", letsMeet.startTimeLabel);
}
else if (buttonIndex == 1)
{
EndTimeLabel.text = #"Whenever";
[letsMeet setEndTimeLabel:EndTimeLabel.text];
}
NSError *error = nil;
if (![managedObjectContext save:&error]) {
}
}break;
// Handle the error.
}
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
NSLog (#"Prepare For Segue StartTime %#", letsMeet.startTimeLabel);
NSLog (#"Prepare For Segue EndTime%#", letsMeet.endTimeLabel);
}
Here is the log:
2013-02-20 21:38:24.253 AppointmentTime[3129:c07] This is the StartTime after selecting 9:30 AM
2013-02-20 21:38:32.325 AppointmentTime[3129:c07] This is the EndTime 12:15 PM
2013-02-20 21:38:32.325 AppointmentTime[3129:c07] This is the StartTime after Selecting BOTH (null)
2013-02-20 21:38:34.069 AppointmentTime[3129:c07] Prepare For Segue StartTime (null)
2013-02-20 21:38:34.069 AppointmentTime[3129:c07] Prepare For Segue EndTime12:15 PM
Q: Why would letsMeet.startTimeLabel show up correct the first time and after selecting EndTime, it changes to NULL. Please note EndTime continues to show the correct Value all the way up to prepareForSegue. Weird!
According to your logs and code , you are entering the switch block twice. Which means you are entering the actionSheet:clickedButtonAtIndex: method twice. So each time you enter the method
letsMeet = (LetsMeet *) [NSEntityDescription insertNewObjectForEntityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext];
statement is executed twice, in turn creating two objects. You can see this by doing a fetch from the store.
So you are checking for properties in two different objects and hence the null.
If you are using just one managed object, you can probably add a check for nil for the object before executing insertNewObjectForEntityForName:inManagedObjectContext:. This will make sure you are using the same object.
If you are using more than one object at the same time use the object id or some unique key to identify your object and manipulate it.
Edit:
You can check for nil with the following code:
if(letsMeet==Nil){
letsMeet = (LetsMeet *) [NSEntityDescription insertNewObjectForEntityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext];
}
This will work only, if the object you are calling the actionSheet:clickedButtonAtIndex: method is always in memory. But since you are persisting you might want to fetch the object from the store and then check for no. of objects.
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext]];
NSArray *objectArray = [managedObjectContext executeFetchRequest:request error:&error]
if(objectArray.count==0){
letsMeet = (LetsMeet *) [NSEntityDescription insertNewObjectForEntityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext];
}else{
letsMeet = (LetsMeet *)[objectArray objectAtIndex:0];
}
Note: If you need to persist only a couple of variables, core-data might be an overkill. Use NSUserDefaults instead and keep it simple.

NSPredicate with 20 last records

I'm working with core data and I try to get last 20 records using setFecthLimit but in these last records I want to get count of unreads, I use this
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:_managedObjectContext];
[request setEntity:entity];
[request setFetchLimit:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isRead = NO OR isRead = NIL"];
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *records = [[_managedObjectContext
executeFetchRequest:request
error:&error] mutableCopy];
but it always return me 20, can anyone help me please?
A simple solution is to remove the predicate. Grab 20 objects and then filter them based on the predicate.
For example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:_managedObjectContext];
[request setEntity:entity];
[request setFetchLimit:20];
NSError *error = nil;
NSArray *records = [_managedObjectContext executeFetchRequest:request
error:&error];
// do some error checking here...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isRead = NO OR isRead = nil"];
// [NSPredicate predicateWithFormat:#"isRead = %#", [NSNumber numberWithBool:NO]];
NSArray *filteredRecords = [records filteredArrayUsingPredicate:predicate];
I think you have a typo in your predicate format:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Post"]
request.fetchLimit = 20;
request.predicate = [NSPredicate predicateWithFormat:#"(isRead == NO) OR (isRead == NULL)"];
NSSortDescriptor = [...insert your sort descriptor code here...]
request.sortDEscriptos = #[sd];
And remember to sort, such that you get the last 20, not just a random 20.

Core Data Optimize Fetch Request

This Fetch Request takes almost 10 seconds to execute on iPhone.
The goal is to fetch 1 random thumbnail from each category.
setting fetchlimit = 1 most of the times return the same thumbnail so I have to fetch all photos from each category.
Any ideas ?
[categoriesArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photos"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"place.subcategory.category == %#", object];
[request setPredicate:predicate];
NSError *error;
NSArray *photosManagedObjectsArray = [managedObjectContext executeFetchRequest:request error:&error];
NSUInteger randomIndex = arc4random() % [photosManagedObjectsArray count];
NSManagedObject *photoObject = [photosManagedObjectsArray objectAtIndex:randomIndex];
UIImage *photoImage = [UIImage imageWithData:[photoObject valueForKey:#"thumbnail"]];
UIImage *resizedImage = [photoImage imageCroppedToFitSize:CGSizeMake(IMAGE_WIDTH, IMAGE_HEIGHT)];
[imagesArray addObject:resizedImage];
[objectsArray addObject:photoObject];
}];
many thanks!
Bill.
You don't need to pull all your objects into an array. Just change
NSArray *photosManagedObjectsArray = [managedObjectContext executeFetchRequest:request error:&error];`
NSUInteger randomIndex = arc4random() % [photosManagedObjectsArray count];
to this
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&error];
NSUInteger randomIndex = arc4random() % count;
Now use fetchOffset to grab just the single object you need.
[request setFetchOffset:randomIndex];
[request setFetchLimit:1];

Resources