I know that I can use #distinctUnionOfObjects to find something like the following in SQL:
SELECT a_value
FROM my_table
GROUP BY a_value;
What I'm looking for is all of the data returned in an array, not just the array of values that matches the group by expression. Essentially, I'm looking for the equivalent of the following SQL query for core data:
SELECT *
FROM my_table
GROUP BY a_value;
It's analog
SELECT 'Status', COUNT(*) FROM 'Records' GROUP BY 'Status':
NSFetchRequest* fetch = [NSFetchRequest fetchRequestWithEntityName:#"Record"];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Record"
inManagedObjectContext:myManagedObjectContext];
NSAttributeDescription* statusDesc = [entity.attributesByName objectForKey:#"status"];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: #"url"]; // Does not really matter
NSExpression *countExpression = [NSExpression expressionForFunction: #"count:"
arguments: [NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: #"count"];
[expressionDescription setExpression: countExpression];
[expressionDescription setExpressionResultType: NSInteger32AttributeType];
[fetch setPropertiesToFetch:[NSArray arrayWithObjects:statusDesc, expressionDescription, nil]];
[fetch setPropertiesToGroupBy:[NSArray arrayWithObject:statusDesc]];
[fetch setResultType:NSDictionaryResultType];
NSError* error = nil;
NSArray *results = [myManagedObjectContext executeFetchRequest:fetch
error:&error];
Found here
You could try using an NSFetchedResultsController object to provide grouping by way of the sectionNameKeyPath construct in the initializer. Note that FRCs are mainly to be used to couple with a table view, but it's not really necessary. This way you could group your results by your sectionNameKeyPath which could be a transient attribute in your model, too.
As a comment, I wouldn't recommend thinking of Core Data in terms of a database, which it isn't. Core Data is built to make it easier for you to persist and manage object relationships. Just because on the iOS it runs on top of SQLite doesn't make it a database replacement.
Reference: NSFRC Reference
I find this method (roughly similar to the accepted answer) to be a little cleaner and easier to understand. This is the SQL equivalent to:
SELECT COUNT(*), a_value FROM my_table GROUP BY a_value;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[MyTable className]];
// create expression for grouped column
NSExpressionDescription *aValueDescription = [[NSExpressionDescription alloc] init];
aValueDescription.name = #"aValue"; // name of key in result dictionary
aValueDescription.expression = [NSExpression expressionWithFormat:#"aValue"];
aValueDescription.expressionResultType = NSObjectIDAttributeType;
// create expression for count
NSExpressionDescription *countDescription = [[NSExpressionDescription alloc] init];
countDescription.name = #"count"; // name of dictionary key in result dictionary
countDescription.expression = [NSExpression expressionWithFormat:#"aValue.#count"];
countDescription.expressionResultType = NSInteger32AttributeType;
// fill out fetch request
fetchRequest.propertiesToGroupBy = #[#"aValue"];
fetchRequest.propertiesToFetch = #[aValueDescription, countDescription];
//fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"count" ascending:NO]]; // not sure why this crashes
fetchRequest.resultType = NSDictionaryResultType; // required for "group by" requests
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
The returned results is an array of NSDictionary. Note that the description name properties can be anything you want - they are just the names of the keys in the returned dictionaries. One can add a predicate to the fetch request to filter rows from the table; this code returns all rows.
Bonus points to anyone who can tell me how to make the sort descriptor work...
I finally found a solution by using NSFetchedResultsController:
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:GROUPBYKEY cacheName:nil]
It will return the sections having grouped objects. Then you can do your operations on it.
You can use Predicate Programming.
EDIT: Sorry you can not use predicates for Group By at least not straight-forward. I just read on the references.
Limitations: You cannot necessarily translate “arbitrary” SQL queries into predicates or fetch requests. There is no way, for example, to convert a SQL statement such as
SELECT t1.name, V1, V2
FROM table1 t1 JOIN (SELECT t2.name AS V1, count(*) AS V2
FROM table2 t2 GROUP BY t2.name as V) on t1.name = V.V1
Related
Suppose I have one entity 'Person' in core data.
Now i want to search all persons. Either firstname beginning match or lastname beginning match.
For eg :
1) Amit Gupta
2) Ajay Gulati
3) Gunjan Aggarwal
Searching for 'Gu' shows names that match firstname first, then those that match lastname
therefore result is :
Gunjan Aggarwal
Ajay Gulati
Amit Gupta
One option : Fetch all objects , store them in array and then sort.
But what if the search results are very big in number
Second option : Use NSFetchedResultsController
This will fetch all matching but not in the required manner(firstname before lastname).
Cant use sort descriptors as it is not sorting on any key, but upon matching.
Can anybody help ?
EDIT :
First name and lastname are two different attributes of 'Person' entity.
Either Firstname matches or Lastname matches.
I want results with 'Firstname' match before than results with 'Lastname' match.
If you use sort descriptor, which 'Key' or 'attribute' will you mention ???
Try to set Sort Descriptors before fetching:
NSSortDescriptor * firstNameDescriptor;
firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor * lastNameDescriptor;
lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
[firstNameDescriptor release];
[lastNameDescriptor release];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:firstNameDescriptor, lastNameDescriptor, nil]];
then fetch your desired persons.
After you got a sorted result, in order to match your searching, you might need to resort it:
- (NSComparisonResult)compare:(id)anotherOne {
// if the first name matches the searching key word, return NSOrderedAscending;
// else return [self.firstName localizedCaseInsensitiveCompare:anotherOne.firstName];
}
then just apply this to your search result array with sortedArrayUsingSelector: method. No testing code available, but I think you can figure it out by yourself then. ;)
Use a transient property sortName in your Core Data object, make it compare the firstName and lastName properties and just return whichever comes first alphabetically.
Now just use this property as your sortDescriptor and you should get the result you are after.
Something like this...
- (NSString *)sortName
{
[self willAccessValueForKey:#"sortName"];
NSString *string = nil;
if ([self.firstName compare:self.lastName] == NSOrderedAscending) {
string = self.firstName;
} else {
string = self.lasttName;
}
[self didAccessValueForKey:#"sortName"];
return string;
}
Use two fetch requests. When you're providing data to the view, display the results of the first fetch request, followed by the results of the second.
I'm new to core data and having some difficulty finding any information about sorting and only fetching non duplicates of a specified attribute.
I have a list of locations and they all have different addresses but some have the same city. i would like to fetch all the cities excluding duplicate cities and in alphabetical order.
Would the best way be to have 2 attributes, one for city and another for locationDetails. in the city attribute it will just have a list of cities with no duplicates and when selecting a city it will fetch all the locationDetails for that city attribute?
Thanks,
Yes the best way will be to have a separate attribute for city. And you don't need to structure your entity as unique. You can take care of sorting and fetching unique in your fetch request.
NSFetchRequest *request=[[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"<your entity name>" inManagedObjectContext:context]];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:[NSArray arrayWithObject:#"City"]];
request.sortDescriptors=[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"City" ascending:YES]];
[request setReturnsDistinctResults:YES];
Note: This request returns an array of dictionaries with City as its key. Don't forget to release the NSFetchRequest after executing the fetch.
EDIT
When the user clicks on a city, store it in some variable (cityName). Now execute another fetch request like:
request.predicate=[NSPredicate predicateWithFormat:#"City = %#",cityName];
You should do this with the same entity. This will fetch only the objects whose city name has been selected. Now, you can either do the fetch in 2 ways:
Fetch the entire entities and then dispay using the format
entityName.Location. In this case you will get an NSArray of
objects
Set the properties to fetch like:
[request setPropertiesToFetch:[NSArray arrayWithObject:#"Location"]];
In the second case, it returns a NSDictionary ( Don't forget to set Result Type as NSDictionaryResultType as before ).
You should structure your Core Data model so that you have a City entity for each unique city and a Location entity with a one-to-one relationship to the relevant City entity (and a one-to-many reverse relationship). The city name itself should not be part of the Location entity. When structured this way, you simply fetch all City entities.
As for sorting, you should add a NSSortDescriptor to your NSFetchRequest. For example:
NSSortDescriptor *sorter;
sorter = [[NSSortDescriptor alloc] initWithKey: #"name" ascending: YES];
[fetchRequest setSortDescriptors: [NSArray arrayWithObject: sortDescriptor]];
I'm not certain how one would go about "cascading" several conditions into an NSPredicate.
I'm fairly new to Core Data and not sure if that's the right way to achieve what I am looking to do.
Here is what I am trying to do:
There is a Photo object that has a whereTook relationship to a Location object, which has an inverse of photosTookThere.
The Photo in turn has an NSNumber attribute called isFavourite.
I'd like to configure an NSFetchRequest that will first check that photosTookThere exists and if so, check each resulting Photo object and return only the ones that are set as favourites.
Here is the code so far:
request.entity = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:context];
request.sortDescriptors = [NSArray arrayWithObject[NSSortDescriptorsortDescriptorWithKey:#"locationId" ascending:YES]];
request.predicate = [NSPredicate predicateWithFormat:#"photosTookThere.#count != 0"];
How would I cascade the second condition into the predicate and return the correct results?
Just put each condition inside of parentheses and connect them with AND.
[NSPredicate predicateWithFormat:#"(photosTookThere.#count != 0) AND (isFavourite == %#)", [NSNumber numberWithBool:YES]];
I'd also recommend changing the name of your "whereTook" relationship to "location", and your "photosTookThere" to simply "photos", that's more in line with convention.
I eventually was able to solve this using a subquery:
request.predicate = [NSPredicate predicateWithFormat:
#"(photosTookThere.#count !=0) AND SUBQUERY(photosTookThere, $Photo, $Photo.isFavourite==1).#count >0"
];
You can very simply use AND and OR in your predicate much like you would if you were specifying a "where" condition in an SQL statement. To check for a NULL, which it looks like you want when comparing against "whereTook", you can compare to nil (whereTook = nil).
Here is the model I have:
http://www.girardet.ch/model.png
My goal is to retrieve all the Quotes with these criterias:
belong to a specific theme : the name_en attribute of Themes
order by relevancy
filtered by Authors (with the alias attribute of Authors)
Here is my code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ThemeEntries" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"relevancy" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// predictate - filter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theme.name_en=%# AND quotes.author.alias=%#",#"mytheme", #"myauthor"];
[fetchRequest setPredicate:predicate];
I get the "to-many key not allowed here" error.
If I instead use this predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theme.name_en=%#, #"mytheme"];
it works well and I can loop over the ThemeEntries that I get and get all my quotes... But it's not filtered by authors.
What can I do to filter by Authors?
Your problem is that the relationship of one of you keypath is a to-many, to-many and the predicate does not know which particular object goes with which.
You have ThemeEntities<<-->>Quotes which is produces a set at each end. The quotes.author.alias keypath says "a set of quotes instances, each linked to author instances which in turn has an alias attribute." The predicate cannot process the set.
You need to use a subquery to jump a to-many keypath. The subquery is essentially a nested predicate which searches a set and returns another set of objects matching the nested predicate. Subqueries are poorly documented but they have the form:
SUBQUERY(collectionName,$collectionElementVariable,expression-with-$collectionElementVariable)
In this case, you are looking for any quote instances that has a author relationship with an alias matching the supplied string. Your predicate would need to look like:
#"theme.name_en=%# AND (0!=SUBQUERY(quotes,$eachQuote,$eachQuote.author.alias=%#).#count)",#"mytheme", #"myauthor"
The subquery says, "Of the set of quotes, take each quote object and test if its author relationship object has an alias attribute matching 'myauthor'. Count the number of quote objects with that match. If the number is non-zero, return TRUE."
You need to use subqueries whenever you walk a keypath across a to-many relationship.
Lets say I have a model similar to the one below, and I need to Fetch all 'Person' of a specific Company.companyName sorted by personRole.roleWeight
This is the model I have at the moment:
Entity: Company
Attributes: companyName
Relationships: companyRole
Entity: Role
Attributes: roleName, roleWeight
Relationships: rolePerson, RoleCompany
Entity: Person
Attributes: personName
Relationships: person Role
Here is a simple diagram of the relationship:
Company --< Role >--< Person
Is there a way to do this?
If I need to modify the model I would be happy to do so. All suggestions are welcome.
Thanks,
You can't sort by role weight because it is possible to have more than one role that fits.
You also can't come at it from the Role (as opposed to the Person) because you have a many-to-many between role and person.
You should re-think your design because having that many-to-many there does not make much sense. A little bit of data de-normalization, changing that many-to-many to a one-to-many and duplicating the rolename and roleweight values would solve the issue.
Update
Assuming you changed the design to:
Company --< Role >-- Person
Then the solution gets much easier:
- (NSArray*)peopleSortedByWeightInCompany:(NSString*)company inManagedObjectContext:(NSManagedObjectContext*)moc
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Role" inManagedObjectContext:moc]];
[request setPredicate:[NSPredicate predicateWithFormat:#"company.name == %#", companyName]];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"weight" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
NSError *error = nil;
NSArray *roles = [moc executeFetchRequest:request error:&error];
[request release], request = nil;
NSAssert2(roles != nil && error == nil, #"Error fetching roles: %#\n%#", [error localizedDescription], [error userInfo]);
NSArray *people = [roles valueForKeyPath:#"#distinctUnionOfObjects.person"];
return people;
}
You basically fetch the Role entities sorted by weight and filtered by the company name. From that array of Role entities you then use KVC to gather all of the person objects on the other end of the relationship which will retrieve them in order.
Is there a reason that Role is the monkey in the middle and not person? It would make this task easier but maybe you have other things you are doing with the data that precludes this option. If each person had one role and one company you could then create a sort descriptor for role.roleWeight and use NSSet's sortedArrayUsingDescriptors: method on the Company in question's employees relationship set. That would give you a new sorted array with a list of all the Person entities attached to a given Company sorted by roleWeight (you might want to include a secondary sort descriptor to take care of matching roleWeights).