Core Data - How to fetch an entity with max value property - iphone

I have a entity Person with a property personId (personId is unique)
How can I fetch the Person with the max personId?
(I want to fetch the person itself not the value of the property)

You set the fetchLimit to 1 and sort by personId in descending order. E.g.:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
fetchRequest.fetchLimit = 1;
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"personId" ascending:NO]];
NSError *error = nil;
id person = [managedObjectContext executeFetchRequest:fetchRequest error:&error].firstObject;

You need to use a NSFetchRequest with a NSPredicate to specify your query...
Adapted from Apple's Predicate Progamming Guide :
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person"
inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
request.predicate = [NSPredicate predicateWithFormat:#"personId==max(personId)"];
request.sortDescriptors = [NSArray array];
NSError *error = nil;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];

The recommended way is to use Apple Recommended Method NSExpression. I would expect that this would be less expensive than using a sort.If you think about it, with a sort you would have to take all the records sort them and keep the maximum one. With an expression you would just have to read through the list and keep in memory the maximum.
Here is an example I use with NSDate
- (NSDate *)lastSync:(PHAssetMediaType)mediaType {
NSEntityDescription *entity = [NSEntityDescription entityForName:kMediaItemEntity inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = entity;
fetchRequest.resultType = NSDictionaryResultType;
NSMutableArray *predicates = [NSMutableArray array];
[predicates addObject:[NSPredicate predicateWithFormat:#"%K=%d", kMediaType,mediaType]];
[predicates addObject:[NSPredicate predicateWithFormat:#"%K=%d", kMediaProviderType,self.mediaProviderType]];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates: predicates];
fetchRequest.predicate = predicate;
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:kSyncTime];
// Create an expression to represent the function you want to apply
NSExpression *maxExpression = [NSExpression expressionForFunction:#"max:"
arguments:#[keyPathExpression]];
// Create an expression description using the maxExpression and returning a date.
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"maxDate"];
[expressionDescription setExpression:maxExpression];
[expressionDescription setExpressionResultType:NSDateAttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
fetchRequest.propertiesToFetch = #[expressionDescription] ; // #[kSyncTime];
NSError *fetchError = nil;
id requestedValue = nil;
// fetch stored media
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
if (fetchError || results == nil || results.count == 0) {
return [NSDate dateWithTimeIntervalSince1970:0];
}
requestedValue = [[results objectAtIndex:0] valueForKey:#"maxDate"];
if (![requestedValue isKindOfClass:[NSDate class]]) {
return [NSDate dateWithTimeIntervalSince1970:0];
}
DDLogDebug(#"sync date %#",requestedValue);
return (NSDate *)requestedValue;
}

The answer given above using NSExpression is correct. Here is the Swift version.
private func getLastSyncTimestamp() -> Int64? {
let request: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest()
request.entity = NSEntityDescription.entity(forEntityName: "EntityName", in: self.moc)
request.resultType = NSFetchRequestResultType.dictionaryResultType
let keypathExpression = NSExpression(forKeyPath: "timestamp")
let maxExpression = NSExpression(forFunction: "max:", arguments: [keypathExpression])
let key = "maxTimestamp"
let expressionDescription = NSExpressionDescription()
expressionDescription.name = key
expressionDescription.expression = maxExpression
expressionDescription.expressionResultType = .integer64AttributeType
request.propertiesToFetch = [expressionDescription]
var maxTimestamp: Int64? = nil
do {
if let result = try self.moc.fetch(request) as? [[String: Int64]], let dict = result.first {
maxTimestamp = dict[key]
}
} catch {
assertionFailure("Failed to fetch max timestamp with error = \(error)")
return nil
}
return maxTimestamp
}
where moc is a NSManagedObjectContext.

Swift 3
let request:NSFetchRequest = Person.fetchRequest()
let sortDescriptor1 = NSSortDescriptor(key: "personId", ascending: false)
request.sortDescriptors = [sortDescriptor1]
request.fetchLimit = 1
do {
let persons = try context.fetch(request)
return persons.first?.personId
} catch {
print(error.localizedDescription)
}

SWIFT 4
let request: NSFetchRequest<Person> = Person.fetchRequest()
request.fetchLimit = 1
let predicate = NSPredicate(format: "personId ==max(personId)")
request.predicate = predicate
var maxValue: Int64? = nil
do {
let result = try self.context.fetch(request).first
maxValue = result?.personId
} catch {
print("Unresolved error in retrieving max personId value \(error)")
}

In addition to Ryan's answer, in Swift today, NSManagedObject's execute(_:) returns a NSPersistentStoreResult object, which need some extra code to retrieve the value:
// Cast `NSPersistentStoreResult` to `NSAsynchronousFetchResult<NSDictionary>`
let fetchResult = moc.execute(request) as! NSAsynchronousFetchResult<NSDictionary>
// Retrieve array of dictionary result
let dictsResult = fetchResult.finalResult
// Retrieve and cast the real result
let key = /* `expressionDescription.name` */
let result = dictsResult.first!.object(forKey: key) as! /* Your type, depending on `expressionDescription.expressionResultType` */
Note: Force unsafe type cast are used above to simplify code, in real case scenario, you should always avoid this.

Related

SWIFT: Checking data IF EXIST before saving into sqlite

I can save data into sqlite database by SWIFT in this way.
for aItem in jsonItems {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let moContext:NSManagedObjectContext = appDel.managedObjectContext
let ModelAnimal = NSEntityDescription.insertNewObjectForEntityForName("TBL_animal", inManagedObjectContext: moContext) as! ModelAnimal
ModelAnimal.wId = aItem["wId"] as? NSNumber
ModelAnimal.wName = aItem["wName"] as? String
do {
try moContext.save()
} catch {
print("Data not Saved in Database")
}
}
But what is the easiest way to check IF EXIST before saving data into sqlite?
With Core Data you check whether an entry already exists by fetching it, with a predicate that uses whatever field(s) would identify a unique entry. Assuming that your wId is unique, you'd do something like this:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"TBL_animal"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"wId = %#", ModelAnimal.wId"];
fetch.predicate = predicate;
NSError *error = nil;
NSUInteger count = [moContext countForFetchReuqest:fetch error:&error];
That'll tell you how many existing objects match your predicate.

CKQueryOperation get the last record

I need to fetch the very last record in cloudkit. Here is my code:
CKContainer *container = [CKContainer containerWithIdentifier:containerID];
CKDatabase *publicDatabase = [container publicCloudDatabase];
CKQuery *query = [[CKQuery alloc] initWithRecordType:recordType
predicate:[NSPredicate predicateWithFormat:#"TRUEPREDICATE"]];
CKQueryOperation *queryOp = [[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.recordFetchedBlock = ^(CKRecord *record)
{
//do something
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
NSLog(#"CKQueryCursor error %#", error);
};
queryOp.resultsLimit = CKQueryOperationMaximumResults;
[publicDatabase addOperation:queryOp];
My question is how can I modify my code to get the very last record in cloudkit?
I'll really appreciate your help
You can sort on the creation dat ascending and then just ask for 1 result like this (code is in Swift):
Adding the sort:
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
Limiting the result:
queryOp.resultsLimit = 1
Objective-C version
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO];
query.sortDescriptors = #[sortDescriptor];
for one record:
queryOp.resultsLimit = 1;
Setting resultsLimit does not seem to work for me, however set it anyway and sorting the results using a timestamp or the record creation date. Then store the results in an array and simply use the first or last item depending on the sort order
CKContainer *container = [CKContainer containerWithIdentifier:containerID];
CKDatabase *publicDatabase = [container publicCloudDatabase];
CKQuery *query = [[CKQuery alloc] initWithRecordType:recordType predicate:[NSPredicate predicateWithFormat:#"TRUEPREDICATE"]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO];
query.sortDescriptors = #[sortDescriptor];
CKQueryOperation *queryOp = [[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.recordFetchedBlock = ^(CKRecord *record)
{
//do something
recordArray.append(record)
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
NSLog(#"CKQueryCursor error %#", error);
let myLastRecord = recordArray[recordArray.count - 1]
};
queryOp.resultsLimit = CKQueryOperationMaximumResults;
[publicDatabase addOperation:queryOp];

iOS - How can I sort the managed objects fetched from CoreData?

This is how i am inserting the data,
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:[DOSnow entityDescription] inManagedObjectContext: proxy.managedObjectContext];
DOCurrentCondition *doSnow = [[[DOSnow alloc] initWithEntity:entityDescription insertIntoManagedObjectContext: proxy.managedObjectContext] autorelease];
NSXMLElement *snowConditionsElement = [[roseElement elementsForName:SNOW] lastObject];
NSArray *snowElements = [snowConditionsElement children];
for (NSXMLElement *snowElement in snowElements)
{
NSEntityDescription * entityDescription = [NSEntityDescription entityForName:[DOPair entityDescription] inManagedObjectContext: proxy.managedObjectContext];
DOPair *pair = [[[DOPair alloc] initWithEntity:entityDescription insertIntoManagedObjectContext: proxy.managedObjectContext] autorelease];
pair.key = [snowElement name];
pair.value = [snowElement stringValue];
[doSnow addConditionsObject: pair];
}
[proxy save];
And this is how i am fetching the data,
- (NSArray *) fetchSnowConditions
{
ApplicationFacade *appFacade = [ApplicationFacade appFacade];
NSManagedObjectContext *context = appFacade.rProxy.managedObjectContext;
NSFetchRequest * request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:[DOSnow entityDescription] inManagedObjectContext:context]];
NSError *error;
NSArray *result = [context executeFetchRequest:request error:&error];
return result;
}
So i am not getting the data in same order as i inserted.
You have to use [NSFetchRequest setSortDescriptor:] to get a meaningful ordering of your results. The NSFetchRequest documentation doesn't say anything about the default order of the results, so it's not a good idea to assume there is any.
Of course, in order to correctly specify the sort descriptor, you probably need to add a field to your managed objects to sort on, and assign a value to it when creating the objects. It could be an incrementing index field, a creation date, or something like that.

Predicate and expression to fetch complex request core data

I have to make a complex core data fetch request but I don't know if it can be made.
This is my scenario: just one entity (Expense) with these attributes:
Cost (NSDecimalNumber)
Deposit (NSDecimalNumber)
Category (NSString)
Paid (Boolean Value)
The request should return the 3 most expensive categories but these are the rules that must be respected:
If Paid == YES, Expense cost should be added to Expense category total
If Paid == NO && Deposit > 0, Expense deposit should be added to Expense category total
If Paid == NO, nothing should be added to Expense category total
Using NSExpression, I'm able to calculate every total per category but it also includes cost of Expenses not paid.
Is there a way to accomplish this?
Thank you so much!
You could, for example, use a NSFetchRequest:
// Build the fetch request
NSString *entityName = NSStringFromClass([Expense class]);
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
which filters only relevant expenses:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(paid == YES) OR ((paid == NO) AND (deposit > 0))"];
request.predicate = predicate;
and sums up the cost and depost attributes:
NSExpressionDescription *(^makeExpressionDescription)(NSString *, NSString *) = ^(NSString *keyPath, NSString *name)
{
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:keyPath];
// Create an expression to represent the function you want to apply
NSExpression *totalExpression = [NSExpression expressionForFunction: #"sum:" arguments: #[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value
expressionDescription.name = name;
expressionDescription.expression = totalExpression;
expressionDescription.expressionResultType = NSDecimalAttributeType;
return expressionDescription;
};
NSExpressionDescription *totalCostDescription = makeExpressionDescription(#"cost", #"totalCost");
NSExpressionDescription *totalDepositDescription = makeExpressionDescription(#"deposit", #"totalDeposit");
// Specify that the request should return dictionaries.
request.resultType = NSDictionaryResultType;
request.propertiesToFetch = #[categoryDescription,
paidDescription,
totalCostDescription,
totalDepositDescription];
and group the results by category and paid status:
// Get 'category' and 'paid' attribute descriptions
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
inManagedObjectContext:context];
NSDictionary *attributes = [entity attributesByName];
NSAttributeDescription *categoryDescription = attributes[#"category"];
NSAttributeDescription *paidDescription = attributes[#"paid"];
// Group by 'category' and 'paid' attributes
request.propertiesToGroupBy = #[categoryDescription, paidDescription];
You'll get paid and unpaid expenses summed up
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
all you need to do is combine (and sort) then:
if (results) {
NSMutableDictionary *combined = [NSMutableDictionary dictionary];
for (NSDictionary *result in results) {
NSString *category = result[#"category"];
BOOL paid = [result[#"paid"] boolValue];
NSDecimalNumber *total = result[paid ? #"totalCost" : #"totalDeposit"];
NSDecimalNumber *sum = combined[category];
if (sum) {
total = [total decimalNumberByAdding:sum];
}
combined[category] = total;
}
NSArray *sortedCategories = [combined keysSortedByValueUsingSelector:#selector(compare:)];
[sortedCategories enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#"Category %#: %#", obj, combined[obj]);
}];
}
else {
NSLog(#"Error: %#", error);
}

Float became 0.0000 when fetched from core data

I created an entity with a float attribute and set it's value to 0.5
+(void)createSportNamed:(NSString *)name withContext:(NSManagedObjectContext *)context{
Sport *newSport = [NSEntityDescription insertNewObjectForEntityForName:#"Sport" inManagedObjectContext:context];
newSport.name = name;
newSport.maxRep = [NSNumber numberWithInt:45];
newSport.endurance = [NSNumber numberWithFloat:0.5f];
NSLog(#"endurance at set = %f",[newSport.endurance floatValue]);
NSError *saveError = nil;
[context save:&saveError];
}
From the log, the value of the float is still 0.5000
But when I fetch it later on, the value somehow became 0.0000
-(NSArray*)createWorkoutForSport:(NSString*)sportName withContext:(NSManagedObjectContext*)context{
NSFetchRequest *request = [NSFetchRequest new];
request.entity = [NSEntityDescription entityForName:#"Sport" inManagedObjectContext:context];
NSPredicate *sportNamePredicate = [NSPredicate predicateWithFormat:#"name == %d",sportName];
request.predicate = sportNamePredicate;
NSError *err = nil;
NSArray *results = [context executeFetchRequest:request error:&err];
Sport *theFetchedSport = [results lastObject];
int totalRep = [[theFetchedSport maxRep]intValue];
float endure = [[theFetchedSport endurance]floatValue];
int set;
NSLog(#"Endurance = %f",endure);
+(void)createSportNamed:(NSString *)name withContext:(NSManagedObjectContext *)context{
Sport *newSport = [NSEntityDescription insertNewObjectForEntityForName:#"Sport" inManagedObjectContext:context];
newSport.name = name;
This clearly shows that the "name" field of your Sport entity is a string.
[NSPredicate predicateWithFormat:#"name == %d",sportName]
And yet here you are using %d, which means int. You're casting a string pointer to an int. You should be using %#. Thus, your fetch request is returning an empty array, and -lastObject is returning nil, which means that [nil endurance] is also nil, which means that [[nil endurance] floatValue] is also nil, or 0.