45

I'm trying to setup my NSFetchRequest to core data to retrieve the unique values for a specific attribute in an entity. i.e.

an entity with the following information:

  name | rate | factor |
_______|______|________|
John   |  3.2 |    4   |
Betty  |  5.5 |    7   |
Betty  |  2.1 |    2   |
Betty  |  3.1 |    2   |
Edward |  4.5 |    5   |
John   |  2.3 |    4   |

How would i set up the request to return an array with just: John, Betty, Edward?

albertamg
  • 28,492
  • 6
  • 64
  • 71
ephilip
  • 1,015
  • 1
  • 10
  • 23

3 Answers3

78

You should use the backing store to help you get distinct records.

If you want to get an array with just John, Betty, Edward here's how you do it:

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"MyEntity"];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:self.managedObjectContext];

// Required! Unless you set the resultType to NSDictionaryResultType, distinct can't work. 
// All objects in the backing store are implicitly distinct, but two dictionaries can be duplicates.
// Since you only want distinct names, only ask for the 'name' property.
fetchRequest.resultType = NSDictionaryResultType;
fetchRequest.propertiesToFetch = [NSArray arrayWithObject:[[entity propertiesByName] objectForKey:@"name"]];
fetchRequest.returnsDistinctResults = YES;

// Now it should yield an NSArray of distinct values in dictionaries.
NSArray *dictionaries = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSLog (@"names: %@",dictionaries);
idmean
  • 14,540
  • 9
  • 54
  • 83
AlleyGator
  • 1,266
  • 9
  • 13
  • AlleyGator, but if I have more than one object with the same attribute (as in the example, more than one Betty), and I don't wanna get repeated values? – Natan R. Aug 27 '12 at 21:02
  • This will give you an array of the three names, with no duplicates. No other data. No rate, no factor. – AlleyGator Aug 28 '12 at 01:06
  • 1
    I missed the returnsDistinctResults property. Thanks. Upvoted the answer, but NSArray doesn't have `allValues` method. So I still need to do a for cycle to get each value from each NSDictionary in the array. – Natan R. Aug 28 '12 at 10:22
  • You missed [fetchRequest setEntity:entity]; – glebd Oct 07 '12 at 01:24
  • 1
    @glebd fetchRequestWithEntityName: eliminates the need for setEntity: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html#//apple_ref/occ/clm/NSFetchRequest/fetchRequestWithEntityName: – AlleyGator Nov 12 '12 at 20:43
  • 1
    This answer didn't work for me at first. Every time I made a fetch request with the NSDictionaryResultType I got zero results back. The problem was that the context needs to be saved before you'll get the results back, so just save context before making this fetch request. – Mark Bridges Nov 07 '13 at 13:57
  • @AlleyGator thanx for answer, can you tell a bit more about backing store or just give the links where I can find info about it? – derpoliuk Mar 12 '14 at 16:20
  • @StasDerpoliuk Look up NSFetchRequest https://developer.apple.com/library/mac/documentation/cocoa/reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html and the Core Data Framework, Programming Guides. Core Data seems pretty daunting but there's less than 30 classes in the framework. https://developer.apple.com/library/mac/documentation/cocoa/reference/CoreData_ObjC/_index.html#//apple_ref/doc/uid/TP40001181 – AlleyGator Mar 14 '14 at 18:24
31

This is a simple approach in Swift 5.x:

// 1. Set the column name you want distinct values for
let column = "name"

// 2. Get the NSManagedObjectContext, for example:
let moc = persistentContainer.viewContext

// 3. Create the request for your entity
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "User")

// 4. Use the only result type allowed for getting distinct values
request.resultType = .dictionaryResultType

// 5. Set that you want distinct results
request.returnsDistinctResults = true

// 6. Set the column you want to fetch
request.propertiesToFetch = [column]

// 7. Execute the request. You'll get an array of dictionaries with the column
// as the name and the distinct value as the value
if let res = try? moc.fetch(request) as? [[String: String]] {
    print("res: \(res)")

    // 8. Extract the distinct values
    let distinctValues = res.compactMap { $0[column] }
}
Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165
  • 1
    Getting this crash on the `if let` on step 7: `Fatal error: NSArray element failed to match the Swift Array Element type` – Akash Kundu Apr 07 '23 at 06:09
  • @AkashKundu Good catch. The example assumes that the type of the distinct column is `String`. If it's a different type, change `as? [[String: String]]` to `as? [[String: Int]]`, for example. I haven't tested this, but I think that should solve your problem. – Johannes Fahrenkrug Apr 07 '23 at 09:18
  • 2
    Ah thanks! Actually i was wrong, i realized when creating the request for my entity (step 3), i needed the type to be`NSFetchRequest`. That fixed it! – Akash Kundu Apr 09 '23 at 23:44
16

You're trying to use Core Data like a procedural data base instead of as an object graph manager as the API intended, so you won't find an easy way to do this.

There isn't a straight forward way to do this in Core Data because Core Data is concerned with objects instead of values. Since managed objects are guaranteed to be unique, Core Data doesn't much care about each object's values or whether they are duplicates or some other object's values.

To find the unique values:

  1. Perform a fetch by specific value. That will give you an array of dictionaries with a key name and a value of the name string itself.
  2. On the returned array in (1) use a set collection operator to return a set of unique values.

So, something like:

NSSet *uniqueNames=[fetchedNameDicts valueForKeyPath:@"@distinctUnionOfSets.name"];

... which will return a set of NSString objects all with a unique value.

TechZen
  • 64,370
  • 15
  • 118
  • 145
  • 2
    +1 That's what I was going to say, except that I'd have chosen `@"distinctUnionOfObjects.name"`. I'm not convinced that there's a difference. – Caleb Aug 09 '11 at 21:07
  • what would the NSExpression and NSExpressionDescription look like for this? – ephilip Aug 16 '11 at 18:24
  • @ephilip -- I'd open another question on that. I can't squeeze that into a comment. – TechZen Aug 16 '11 at 21:23
  • Though the answer is correct it's inefficient in terms of processing time and memory usage. I'd take a look at the answer by @AlleyGator. – Rodolfo Cartas Sep 18 '12 at 18:17