41

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)

hypercrypt
  • 15,389
  • 6
  • 48
  • 59
Eyal
  • 10,777
  • 18
  • 78
  • 130

7 Answers7

68

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;
hypercrypt
  • 15,389
  • 6
  • 48
  • 59
  • 4
    If you don't have an index on the attribute being sorted, then this technique is more expensive, O(n log n), than scanning a list for a max value, O(n), as described in @Uilleann's answer. That said, if you do have an index on the attribute being sorted, then both techniques should should be the same. – Doug Richardson Mar 12 '15 at 23:10
  • Is it worth setting `fetchBatchSize` to 1 also, or is that implicit in `fetchLimit` being 1? – Benjohn Jun 02 '15 at 13:37
  • Shouldn't make a difference – hypercrypt Jun 03 '15 at 12:29
24

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];
Martyn Davis
  • 625
  • 1
  • 10
  • 16
Uilleann
  • 506
  • 4
  • 14
  • Is it necessary to set `sortDescriptors` to an empty array? – Abhi Beckert Jan 14 '21 at 01:27
  • @AbhiBeckert You could normally add NSSortDescriptors to specify how you want the returned objects sorted. In this case though, it doesn't make sense to add sort descriptors, as the query/predicate is asking for the max value of personId, so only one object will be returned. – Uilleann Feb 16 '21 at 23:29
18

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;
}
Ryan Heitner
  • 13,119
  • 6
  • 77
  • 119
  • Looks like the approach is really the best one however it seems it does not work on in-memory store: http://stackoverflow.com/questions/19301181/exception-raised-by-nsexpressiondescription-with-core-data-in-memory-store – oradyvan Nov 29 '16 at 17:07
  • It worked for me until I migrated project to Swift 3. Apple, for greater good, introduced strongly typed fetch requests, which do not work with this approach, causing `Could not cast value of type 'NSKnownKeysDictionary1' (0x106019870) to 'NSManagedObject' (0x106019b18).` error. Perhaps the work around is to use `execute` method of MOC, but it seems too low level, and I will revert to 'sort' method. – Tomek Cejner Dec 29 '16 at 12:46
  • i had the same problem after fetching downcast to--> as! [NSDictionary] that solve my problem – Eric Yu Feb 21 '17 at 21:23
  • Note that this solution only works for data that has been saved to the backing store. if you have unsaved objects in the MOC, they will not be part of the max calculation. – sidekickr Apr 19 '18 at 19:33
8

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.

jarora
  • 5,384
  • 2
  • 34
  • 46
4

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)
}
Rajat Jain
  • 1,002
  • 1
  • 15
  • 24
2

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)")
}
hamada147
  • 495
  • 1
  • 8
  • 14
0

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.

ix4n33
  • 526
  • 4
  • 10