a predicate to filter out similar objects by an attribute - iphone

I"m performing a regular NSFetchRequest without any predicate to fetch 100 managed objects (flights), each Flight entity has an attribute of type NSString named (flightCode) and this attribute is not unique, so 2 flight objects may have the same flightCode .
However, I want to fetch all flight objects filtering out flights that have the same flightCode by taking only one flight from similarities, i.e.
if the fetch request returned 5 flights as follows:
flight1: flightCode = ABC
flight2: flightCode = AA
flight3: flightCode = ABC
flight4: flightCode = ABC
flight5: flightCode = DEF
then the fetch request must filter out any two of the 3 flights that have the flightCode ABC and take only any random one of these 3.
what is the required NSPredicate for this filtering ?
p.s. flights: 1, 3 & 4 may be different in other attributes, i.e. flight1's name may be different from flight3's name.
thanks in advance.

If you just want a list of all distinct flight codes, this may do what you want:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Flight"
inManagedObjectContext:moc];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity.name];
request.resultType = NSDictionaryResultType;
request.returnsDistinctResults = YES;
request.propertiesToFetch = #[ entity.propertiesByName[#"flightCode"] ];
Note that returnsDistinctResults only works if propertiesToFetch is set, and propertiesToFetch only works if resultType is NSDictionaryResultType.
REVISED
If you want full Flight objects, but only one for each distinct flight code, I don't think you can do that directly. Perhaps you can ask for both the object ID and the flight code, group by flight code, and take the minimum object ID, to get one object ID for each flight code. Then you can turn those object IDs into full objects one by one using objectForID: on the managed object context. I would try something like this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Flight"
inManagedObjectContext:moc];
NSExpressionDescription *objectIDProperty = [[NSExpressionDescription alloc] init];
objectIDProperty.name = #"objectID";
objectIDProperty.expression = [NSExpression expressionForFunction:#"min:"
arguments:#[ [NSExpression expressedForEvaluatedObject] ]];
objectIdProperty.expressionResultType = NSObjectIDAttributeType;
NSAttributeDescription *flightCodeProperty = entity.propertiesByName[#"flightCode"];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity.name];
request.resultType = NSDictionaryResultType;
request.returnsDistinctResults = YES;
request.propertiesToFetch = #[ flightCodeProperty, objectIDProperty ];
request.propertiesToGroupBy = #[ flightCodeProperty ];
I cribbed a lot of that from this answer. I have no idea if it works, or if I'm even on the right track. If it runs at all, but doesn't quite give the right output, remember that you can see the SQL it's executing by adding -com.apple.CoreData.SQLDebug 1 as a command-line argument.

youll want to make a NSMuteableArray for all of your flights and then a separate NSMuteableArray to keep track of which elements you have already seen
pseudocode:
NSMuteablearray flights
NSMuteablearray alreadySeen
for (item in flights) {
if (alreadySeen containsObject:item)
flights removeObject:item
else
alreadySeen addObject:item
}

Related

Stackmob SMQuery OR conditioning: "Sort cannot be used with OR statements stackmob"

I am using Stackmob SMQuery and trying to have a Query with 4 OR conditions and sort all the results after all. My code is as below. The problem is
SMQuery *query = [[SMQuery alloc] initWithSchema:STACKMOB_SCHEMA_TESTIMONIAL];
[query where:#"friend_id" isIn:[friendFacebookIds arrayByAddingObject:[KKKeychain getStringForKey:#"facebookId"]]];
// WHERE (privacy == Public OR (privacy == Private AND friend_id == myFacebookId) OR (privacy == Restricted AND myFacebookId NOT IN restricted_Ids))
SMQuery *subQuery0 = [[SMQuery alloc] initWithSchema:STACKMOB_SCHEMA_TESTIMONIAL];
SMQuery *subQuery1 = [[SMQuery alloc] initWithSchema:STACKMOB_SCHEMA_TESTIMONIAL];
SMQuery *subQuery2 = [[SMQuery alloc] initWithSchema:STACKMOB_SCHEMA_TESTIMONIAL];
SMQuery *subQuery3 = [[SMQuery alloc] initWithSchema:STACKMOB_SCHEMA_TESTIMONIAL];
[subQuery0 where:#"privacy" isEqualTo:nil]; // just in case no privacy was set, assume it is public
[subQuery0 orderByField:#"createddate" ascending:NO];
[subQuery1 where:#"privacy" isEqualTo:[NSNumber numberWithInt:TTPrivacyLevelPublic]];
[subQuery1 orderByField:#"createddate" ascending:NO];
[subQuery2 where:#"privacy" isEqualTo:[NSNumber numberWithInt:TTPrivacyLevelPrivate]];
[subQuery2 where:#"user_id" isEqualTo:[KKKeychain getStringForKey:#"facebookId"]]; // for private post, I can only see posts that were created by me
[subQuery2 orderByField:#"createddate" ascending:NO];
[subQuery3 where:#"privacy" isEqualTo:[NSNumber numberWithInt:TTPrivacyLevelRestricted]];
[subQuery3 where:#"restricted_user_ids" isNotEqualTo:[KKKeychain getStringForKey:#"facebookId"]]; // make sure I'm not restricted to see
[subQuery3 orderByField:#"createddate" ascending:NO];
// combine subquery to main query
[query and:[[[subQuery1 or:subQuery2] or:subQuery3] or:subQuery0]];
[query orderByField:#"createddate" ascending:NO];
....
So if I commented the last line "[query orderByField:#"createddate" ascending:NO];" then it works fine. But then the results will be a combination of 4 different dataset which is not sorted in createdate order. I also tried to sort in each subquery but the result was also incorrect because they are only sorted inside each subquery, not combining with other results as well.
Thanks.
The database doesn't support executing a query that includes both OR and sorting - this is a database limitation. Instead, perform the query on StackMob, then locally sort the results by createddate using sortedArrayUsingComparator or something similar.

Core Data NSSet returning empty array of objects

UPDATE: I had ghost entries in my database of games without players from before I implemented Players at all. It was returning those entries not the latest entries with the updated Model
I am working to get a Core Data database working and having some trouble with relationships. I'm very new to the whole thing so don't put anything past me I may no even understand the basics. But here we go anyway.
This is where I create a new Game Entity. The Players is a to-may relationship to Several Player entities that were selected and stored in an array.
Game *newGame = [NSEntityDescription insertNewObjectForEntityForName:#"Game" inManagedObjectContext:[SDDataManager dataManager].managedObjectContext];
[newGame setGameType:#"multipleChoice"];
[newGame setDate:[NSDate date]];
NSSet *playersSet = [NSSet setWithArray:players];
[newGame setPlayers:playersSet];
[newGame setCards:[NSKeyedArchiver archivedDataWithRootObject:selectedCards]];
NSError *error;
[[SDDataManager dataManager].managedObjectContext save:&error];
NSLog(#"New Game Error: %#",[error localizedDescription]);
The problem is that when I call it out of the database like this:
NSFetchRequest *requestSavedGame = [NSFetchRequest fetchRequestWithEntityName:#"Game"];
[requestSavedGame setFetchLimit:1];
NSError *error;
NSArray *loadedGame = [[SDDataManager dataManager].managedObjectContext executeFetchRequest:requestSavedGame error:&error];
NSLog(#"Load Game Error: %#",[error localizedDescription]);
Game *game = [loadedGame objectAtIndex:0];
NSLog(#"Players Set: %#",game.players);
NSLog(#"Players: %#",[game.players allObjects]);
The Players: is empty?? It returns this exactly:
Players Set: Relationship 'players' fault on managed object (0xd023b70) <Game: 0xd023b70> (entity: Game; id: 0xd023180 <x-coredata://E6A82377-31D2-4D11-B890-B3FDC5A03E0E/Game/p1> ; data: {
cards = <62706c69 73743030 d4010203 0405086e 6f542474 6f705824 6f626a65 63747358 24766572 73696f6e 59246172 63686976 6572>;
currentPlayer = 0;
date = "2012-03-27 18:20:07 +0000";
gameType = multipleChoice;
players = "<relationship fault: 0xd01fd60 'players'>";
})
Players: ( )
I have no understanding why the players is a full array, then a full set when it goes in but when it comes out the [set allobjects] returns an empty array...
I would suggest you to use valueForKey for whatever key.
In core data fault is not an error, it means you are trying to access from core data which does not exist. and i would say you should use setPropertiesToFetch which specifies which properties should be returned by the fetch.
The message saying that it is a fault does not mean that it is empty. It simply means that the data has not been loaded from the database. If you access it like normal, you should see your data there. Try this and see what it says:
NSLog(#"Players count: %i", [game.players count]);
Read about faults here: Core Data Programming Guide - Faulting and Uniquing

add two values with [[object valueForKey:#""] componentsJoinedByString:#"\n"];

hy everyone,
i really need a helping hand here.
here is my problem:
i have an array with objects which each has two attributes:
1 is an NSSTring name
2 is an NSSNumber price
now i want to run through the array and add all the values to a new string:
the problem is i need the following output.
name (from object 1) : price (from object 1) then new line (\n)
name (from object 2) : price (from object 2) then new line (\n) ... and so on...
would be great if someone could help me here.
with kind regards, thomas
Try this:
// This contains your objects with names and prices;
NSArray *productArray;
...
// |listOfProducts| will be your string with all names and prices.
NSMutableString *listOfProducts = [[NSMutableString alloc] init];
// Build the list of products
for (Product *product in productArray) {
[listOfProducts appendFormat:#"%#: $%0.2f\n", product.name, product.price];
}

Core data only storing last object of JSON feed

I´m using Core Data as local storage in my app. I´ve set it up properly and made subclasses of NSManagedObject for each entity. However, when I´m trying to insert values into my store, it only inserts the last object from my JSON feed.
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger showIndex = 0;
for(NSString *showName in shows){
showObject = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
showObject.name = showName;
showObject.iD = [[res valueForKeyPath:#"Show.Id"]objectAtIndex:showIndex];
showObject.desc = [[res valueForKeyPath:#"Show.Description"]objectAtIndex:showIndex];
showObject.activityType = [[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:showIndex];
showIndex++;
}
This only stores the last object from my JSON feed. Any idea why?
EDIT: It works fine when I do this:
res = [JSONHandler requestJSONResponse:jsonString];
shows = [res valueForKeyPath:#"Show.Name"];
NSUInteger index = 0;
for(NSString *showName in shows){
show = [NSEntityDescription insertNewObjectForEntityForName:#"Show" inManagedObjectContext:managedObjectContext_];
[show setValue:showName forKey:#"name"];
[show setValue:[[res valueForKeyPath:#"Show.Id"]objectAtIndex:index] forKey:#"iD"];
[show setValue:[[res valueForKeyPath:#"Show.Description"]objectAtIndex:index] forKey:#"desc"];
[show setValue:[[res valueForKeyPath:#"Show.ActivityType"]objectAtIndex:index] forKey:#"activityType"];
index++;
}
It´s basically the same thing, isn´t it? But I want to use subclasses of NSManagedObject instead of doing like I did above. Because in the snippet above show is NSManagedObject *show instead of what it should be: Show *show.
How many shows are there? You can find this by doing: NSLog(#"Number of shows: %d.", shows.count);, assuming that shows is an NSArray. It could be that your Core Data code is fine and the JSON parsing itself is at fault.
EDIT: Also, are you correctly saving the changes to the persistent store?
Usually when you see just one of several objects being saved like this, the problem is that a relationship that should be to-many is improperly set as to-one. No matter how many objects you try to add to the relationship, only the last one is set because the relationship can hold only one value.
I think in this circumstance the problem is most likely in the code of the custom subclass instead of the data model itself given that the data model works with generic NSManagedObjects.

Segmented Controller (Multiple Choices) with Core Data

I have a Core Data based application which is built around one main entity. There are several other entities which are connected to it, one of which is an Entity called "Notes".
This Notes entity has a Date (NSDate), a Description (NSString), and one other attribute. This attribute is to have 4 possible options, of which each entity will have at least 1 and possibly all 4.
I was thinking that when a Note is being created, there could be a Segmented Controller with the 4 possible options. (Is it even possible to select multiple buttons here?)
I further want to be able to sort all of these notes by this option. That is, to create a fetch request that returns only the Notes that had Option 3 selected, for example. (Even if they also had Option 2, or even all 4 options selected.)
Any suggestions on what the best way to implement this is?
Thanks.
To use masks for storing multiple selections, you might do something like the following:
NSInteger const kNoteOptionTypeOne = 0x1; // 0000 0001
NSInteger const kNoteOptionTypeTwo = 0x2; // 0000 0010
NSInteger const kNoteOptionTypeThree = 0x4; // 0000 0100
NSInteger const kNoteOptionTypeFour = 0x8; // 0000 1000
For multiple selections, you would still store your combined mask as an NSNumber, e.g.
NSInteger mySelectionsMask = (kNoteOptionTypeOne | kNoteOptionTypeFour); // 0x0 | 0x4 = 1001 = 9
NSNumber *mySelections = [NSNumber numberWithInt:mySelectionsMask];
This mySelections value will be unique to some combination of the four options. You can go back from this combined mask to individual masks, in order to select different buttons of a segmented control, e.g.:
if ([mySelections intValue] == (kNoteOptionTypeOne | kNoteOptionTypeFour)) {
// select buttons one and four of the segmented control
}
else if some other combination { etc. }
Or:
if (([mySelections intValue] & kNoteOptionTypeOne) == kNoteOptionTypeOne)
// select button one
if (([mySelections intValue] & kNoteOptionTypeTwo) == kNoteOptionTypeTwo)
// select button two
...
As this is stored as an NSNumber, you will be able to sort on it with an NSSortDescriptor instance as described above.
First, instead of trying to use one attribute to collect four possible options, I would use four separate boolean attributes. This will also allow you to filter your fetch requests very easily.
To configure each boolean, I would use a UIButton or UISwitch. The UISegmentedControl does not support multiple selection.
Use an enumeration to define four options, e.g.:
typedef enum _NoteOptionType {
kNoteOptionTypeOne,
kNoteOptionTypeTwo,
kNoteOptionTypeThree,
kNoteOptionTypeFour,
kNoteOptionTypes,
} NoteOptionType;
These will be numbered 0 through 5.
Core Data stores integers as NSNumber instances. You could perhaps keep an attribute in your Note entity that is called optionType, which stores the NSNumber equivalent of a NoteOptionType value.
You can convert these to NSNumber options through something like, for example, [NSNumber numberWithInt:kNoteOptionTypeOne].
You could write a convenience method to convert a NoteOptionType to a string to put into a UISegmentedControl, e.g.:
+ (NSString *) keyForNoteOptionTypeTag:(NoteOptionType)optionTypeTag {
if (optionTypeTag == kNoteOptionTypeOne)
return [NSString stringWithFormat:#"First"];
else if (optionTypeTag == kNoteOptionTypeTwo)
return [NSString stringWithFormat:#"Second"];
...
return [NSString stringWithFormat:#"Undefined"];
}
Reference it like so:
NSLog(#"second option is: %#", [Note keyForNoteOptionTypeTag:kNoteOptionTypeTwo]);
In your fetch, you can use the NSNumber values you put in your Core Data store as criteria on which to sort, through the use of an NSSortDescriptor, e.g.:
NSSortDescriptor *optionTypeDescriptor = [[NSSortDescriptor alloc] initWithKey:#"optionType" ascending:YES selector:nil];
NSArray *sortDescriptors = [NSArray arrayWithObjects:optionTypeDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[optionTypeDescriptor release];