Core Data Where clause between two tables? - ios

I have two tables that I would like to get data and from by seeing if they match a particular attribute.
I want to write a predicate or something like this but I do not seem to be able to come up with a solution: #"Data.item == Item.code".
The reason why I don't use relationships is because my database was imported from mysql. So all the data is coming from outside of the app its being synced from downloaded mysql tables.
---------------------------EDIT-------------------------
What I have tried so far lots of things here is the crappy way I am doing this now perhaps from this you can understand more of what I am trying to do .
NSPredicate * Newpredicate = [NSPredicate predicateWithFormat:#"hid == 2"];
NSArray *row2 = [db DLook:Newpredicate table:#"Data"];
for (NSManagedObject *data in row2) {
NSLog(#"\n\n\n\nid\n\n\n\n: %#", [data valueForKey:#"id"]);
NSString *itemToCode = [data valueForKey:#"item"];
NSPredicate *itemPredicate = [NSPredicate predicateWithFormat:#"code == %#",itemToCode];
NSArray *itemRow = [db DLook:itemPredicate table:#"Item"];
for (NSManagedObject *item in itemRow) {
NSLog(#"\n\n\n\ncode : %#\n\n\n\n",[item valueForKey:#"code"]);
}
// NSLog(#"id: %#", [data valueForKey:#"id"]);
//NSManagedObject * itemhid= [data valueForKey:#"testRel"];
//NSLog(#"code: %#",[itemhid valueForKey:#"code"]);
}
NSLog(#"\n\n\n\n%d\n\n\n\n",[row2 count]);
The DLook is a convince method that just fetches the data using the predicate on the table that I pass. Then take the returned area of NSmanaged objects looping through them.
I wish I could just make a magical relationship that would let me get a all the Item.data that match the Data.items!!!
I don't want to do it like this I want to make a relationship that would work like that.
Help
Thanks

Your equality in the predicate would only be true if the two objects are actually the same object. You could do this:
[NSPredicate predicateWithFormat:#"item = %#", code];
However, you should really try to get the relationships right when importing your model. Then you would not even have to do a fetch from the store but just use
dataObject.item;
So, when you import do something like this:
// get one data object from your source
// get the corresponding item object from your source
Data *dataObject = [NSEntityDescription insertNewObjectForEntityForName:#"Data"
inManagedObjectContext:self.managedObjectContext];
Item *itemObject = [NSEntityDescription insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:self.managedObjectContext];
[dataObject addItemObject:itemObject];

Related

Get distinct entity objects from NSMutableArray using NSPredicate in iPhone sdk [duplicate]

This question already has answers here:
Removing duplicates from NSMutableArray
(9 answers)
Closed 10 years ago.
I have an NSMutableArray which has entity class object as its objects.
Now I want to remove the distinct objects from it. Consider following example
Entity *entity = [[Entity alloc] init];
entity.iUserId = 1;
entity.iUserName = #"Giri"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 2;
entity.iUserName = #"Sunil"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 3;
entity.iUserName = #"Giri"
[arr addObject:entity];
Now I want only two objects in the Array by removing the duplicate iUserName. I know the way by iteration but I want it without iterating it like predicate or some other way.
If anyone knows then please help me.
I had tried using [arr valueForKeyPath:#"distinctUnionOfObjects.iUsername"]; but it does not return me the entired object.
This question is totally different than the questions which are asked previously. Previously asked question is for getting the distinct objects is correct but they uses looping & I don't want this. I want it from NSPredicate or any other simple option which avoids looping.
EDIT: You can't do what you want to without looping over the array manually and building up a new array. The answer below won't work because it assumes that there are only at most two duplicates.
NSMutableArray *filteredArray = [NSMutableArray array];
for (Entity *entity in arr)
{
BOOL hasDuplicate = [[filteredArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", entity.iUserName]] count] > 0;
if (!hasDuplicate)
{
[filteredArray addObject:entity];
}
}
This will look for duplicates in the filtered array as it builds it.
Begin Original Answer
You can't use an NSSet because the Entity instances would have to return NSOrderedSame in compareTo:, which isn't a good idea since you shouldn't use names as unique identifiers.
You can use predicates, but they'll still loop over the array in an O(n^2) time without some optimization.
NSArray *filteredArray = [arr filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Entity *evaluatedObject, NSDictionary *bindings) {
return [[arr filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", evaluatedObject.iUserName]] count] > 1;
}]];
That'll work fine. You could make it even faster by sorting the array by the iUserName property first and doing a linear scan over the sorted array (stopping when you see the first duplicate). That's a lot of work if you're dealing with small sample sizes (say, under ten thousand or so). It's probably not worth your time, so just use the code above.
Well you have a few options (that I can think of).
Use a NSSet instead of a NSArray.
Use a for loop (but you don't want to iterate through the array)
Use a predicate search iUserName to see if the name exists before adding it to the array.
Something like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iUserName == 'Giri'"];
NSArray *searchArray = [arr filterUsingPredicate:predicate];

Any way to optimize simple NSFetchRequest for single object?

I'm dong data processing in a child moc in a background queue. I need to query the database by ID so that I can differentiate updating-existing-object from creating-new-object. I found most of the time(the total processing time is about 2s for 50 items) is consumed by executeFetchRequest:error:. The NSPredicate is of the simplest form — only to match a single ID attribute(ID attribute is already indexed), and the NSFetchRequest should return one or none(ID is unique). Is there any way to optimize this kind of NSFetchRequest?
Here is my current code:
+ (User *)userWithID:(NSNumber *)ID inManagedObjectContext:(NSManagedObjectContext *)context {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ID == %#", ID];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:1];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
abort();
}
if ([users count] == 1) {
return [users objectAtIndex:0];
} else if ([users count] > 1) {
// Sanity check.
…
} else {
return nil;
}
}
As #ChrisH pointed out in comments under the question, doing a fetch for every ID is no good. So I changed my processing flow to this:
Enumerate data the first time to extract IDs.
Do a single fetch to fetch all existing users matching IDs and put them in a dictionary keyed by ID(named as existingUsers).
Enumerate data the second time to do the real processing: in each iteration, either update one existing user found in existingUsers or create a new user, add it into existingUsers if it is new.
The code is almost doubled, but so is the performance. Really good tradeoff!
To expand on my comment to the original question, it's not efficient to repeatedly perform fetch requests with Core Data when importing data.
The simplest approach, as #an0 indicated, is to perform one fetch of all the existing objects you will be checking against, and then constructing an NSDictionary containing the objects with the attribute you will be checking as keys. So sticking with the original User and userID example:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
//handle appropriately
}
NSMutableDictionary *userToIdMap = [NSMutableDictionary dictionary];
for (User *user in users){
[userToIdMap setObject:user forKey:user.ID];
}
Now in your method that processes new data you can check the userToIdMap dictionary instead of making fetch requests.
A more sophisticated approach, suited to larger data sets, is outlined in the Core Data Programming Guide's Efficently Importing Data. Take a look at the section called 'Implementing Find-Or-Create Efficiently'. The approach suggested by Apple here misses out some code regarding how to walk the arrays you create, and my solution to that problem is in this SO question: Basic array comparison algorithm

Updating a particular attribute of core data

I am developing an application where i used core data framework for the purpose of maintaining a database. My entity contains three attributes called: name, start time and end time of a list of applications. I am getting the correct values for name and start time attribute.
Now my problem is my end time attribute should contain the value of the next entries start time value. If anybody having any idea about this please let me know.
Thanks
You can leave the endTime attribute blank until you create the next entity. In the +Create category on the entity, get the last/first object (assuming you are using ordered entities) and update the endTime with the same value used for the new startTime.
If your objects are not ordered it could be a bit tricky since all the entities are in a set. But if ordered, you are good since NSOrderedSet responds to lastObject (and firstObject).
Enjoy,
Damien
EDIT: Here is an example factory method that either 1) returns the existing stock entity for a stock symbol or 2) creates a new entity for that symbol. Pretty easily modified to get entities and select the first/last depending on your sort order. Again see the Core Data classes from Prof. Hegarty.
+ (Stock *)stockForSymbol:(NSString *)symbol inManagedObjectContext:(NSManagedObjectContext *)context {
Stock *stock = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Stock"];
request.predicate = [NSPredicate predicateWithFormat:#"symbol = %#",symbol];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"symbol" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || [matches count] > 1) {
// handle error
} else if ([matches count] == 0) {
stock = [NSEntityDescription insertNewObjectForEntityForName:#"Stock" inManagedObjectContext:context];
stock.symbol = symbol;
stock.strategyPosition = [NSNumber numberWithInt:StrategyPositionFlat];
stock.userPosition = stock.strategyPosition;
stock.userOwns = [NSNumber numberWithBool:NO];
} else {
stock = [matches lastObject];
}
return stock;
}

Core Data: import a tree structure with find or insert / duplicate entries

I have a list of Places from a rails app that I'm trying to import in an iOS5 app. Each Place has a parent which is a Place itself.
I'm trying to import that JSON data with Core Data using a dictionary
- (void)initWithDictionary:(NSDictionary *)dictionary {
self.placeId = [dictionary valueForKey:#"id"];
id parent = [dictionary objectForKey:#"parent"];
if (parent && parent != [NSNull null]) {
NSDictionary *parentDictionary = parent;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %#", [parentDictionary objectForKey:#"id"]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, nil);
if ([matching count] > 0) {
self.parent = [matching objectAtIndex:0];
} else {
self.parent = [NSEntityDescription insertNewObjectForEntityForName:#"Place" inManagedObjectContext:self.managedObjectContext];
[self.parent initWithDictionary:parentDictionary];
}
}
}
fetchedWithPredicate is a method defined as such
NSArray* fetchedWithPredicate(NSString *entityName, NSManagedObjectContext *context, NSPredicate *predicate, NSError **error) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesPendingChanges:YES];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setPredicate:predicate];
NSArray *result = [context executeFetchRequest:request error:error];
return result;
}
I also have a validation method in Place.m to make sure I don't create to place with the same placeId (placeId is the id on the server side).
- (BOOL)validatePlaceId:(id *)value error:(NSError **)error {
if (*value == nil)
return YES;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %# AND (NOT self IN %#)", *value, [NSArray arrayWithObject:self]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, error);
if ([matching count] > 0) {
return NO;
}
else {
return YES;
}
}
To import the data, I fetch all places from the server, returned in JSON format.
Each Place has its own information, plus a child node with informations about the parent, which means that each parent of multiple children will appear multiple times. It looks like
{ "id": 73,
"name": "Some place",
"parent": { "id": 2,
"name": "Parent's name"}
}
I thought the above code which does kind of a "find or create", with a fetch including unsaved changes, would be alright.
But it still attempt to create multiple entries for some places (and fails to since there's a validation in place). Looking deeper, it indeed insert different core data objects for the same placeId (different pointers), but I don't know why.
Thanks
It sounds like you already have a unique index on id (which is good obviously). I think it is that you are not saving the newly inserted creations to core data prior to expecting it to be returned via fetch. The simple (if perhaps not too performant depending on having lots of rows) would be to add a saveContext call right after each object is inserted/inited.
Another way would be to do it in two passes, first entirely in memory where you create a separate dictionary where the key is the id, and the object is the value. That way you'd be able to ensure each id was only in there once. After they're all in that dictionary, you can then easily (or easier, perhaps) add them all to Core Data.
So, after a bit more investigation, it's due to the fact that I was sorting my data by name...
So if a place A had 5 children, and 3 of them had a name that was before A's name, the code would:
create those 3 children with a parent that don't have any parent itself (because my json doesn't return infos about the parent's parent)
create A
create the 2 other children with A as parent (probably because of the way it's sorted, but that doesn't change the conclusion), so a parent that does have a parent
Now we have 2 objects A, one with a parent, and one without a parent, which Core Data consider has 2 objects.
The easy way out: my tree is a nested set, so I just have to sort places by the left value, and this way I'll always create parents before children.
The "sort by name" wasn't part of my description, so I'll leave scc's answer as accepted :)

Core Data / Range Update

Here is a simple mySQL request :
UPDATE myTable SET X=X-1 WHERE (X>100)
Now my question goes like this.
How would I write a similar request in Core Data ?
I am starting to find out my way in Core Data; but the last part of the above SQL order gives me problems in Core Data.
In other word : how do I handle the WHERE (X>100) part.
If the mySQL request concerned one record only, I would use some thing like this :
[matchItem setValue:VALUE forKey:#"X"];
But how do I do if is a range of records, like here, where I want to perform some actions for all records where X>100.
Use an NSPredicate and add it to your NSFetchRequest.
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"X > %#", [NSNumber numberWithInt:100]];
Iterate through your results array and set the value as appropriate.
for (NSManagedObject *match in resultsArray) {
NSInteger newValue = [[match objectForKey:#"X"] intValue] +1;
[match setValue:[NSNumber numberWithInt:newValue] forKey:#"X"];
}

Resources