I am trying to write a fetch routine for data from Core Data. I have managed to fetch all the data in the database and display this in a table view but, how do you fetch only some of the data based on a query?
for example, in SQL:
Select * from DB where name = 'bob';
you'll want to use NSPredicate for that. something along these lines
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(name == %#)", name];
[fetchRequest setPredicate:predicate];
Related
I have 2 tables which are linked between themselves with many-to-many relationship.
SQL equivalent doesn't exist because it requires the third table to split a many-to-many relationship to two one-to-many relationships.
For example, I have two core data entities: category with a property (an array of items) and item with a property (an array of categories).
I need to get all the categories except of "empty" categories (when there is no item of this category).
My current temporary solution looks like an incorrect one. I use NSFetchRequest to get all the categories. Then I delete from this array all the categories with an empty item array manually using for-each.
In order to get all categories except the empty ones you could just use a predicate like this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Category"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"items.#count > 0"];
NSArray *categories = [context executeFetchRequest:fetchRequest error:NULL];
My solution:
[NSPredicate predicateWithFormat:#"ANY items != nil"]
I have the following relationship in Core Data:
What's the correct NSPredicate to get all courses having at least one student over the age of 25?
[NSPredicate predicateWithFormat:#"ANY students.age > %d", 25]
should work (as predicate for a fetch request for the "Course" entity).
So here's my problem, I'm brand new to iOS's CoreData, so I'm not really sure how to optimize, but I've seen a lot of the other questions here, and I've tried to optimize as best as I could, but for some reason, it still takes almost 5 seconds to fetch 100 rows!
here's the code where it's being called:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self titles:searchText];
}
-(NSArray *)titles:(NSString *)toMatch
{
NSMutableArray * retval = [[NSMutableArray alloc] init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(toMatch.length>2)
{
NSString *LabelText =toMatch;
NSString *compareString = #"";
for (int i = 0; i<LabelText.length; i++)
{
int letter = [LabelText characterAtIndex:i];
NSString *blah;
if(letter<100)
{
blah = [NSString stringWithFormat:#"0"];
blah = [blah stringByAppendingFormat:#"%d", letter];
}
else
blah = [NSString stringWithFormat:#"%d", letter];
compareString= [compareString stringByAppendingString:blah];
compareString = [compareString stringByAppendingString:#","];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate;
if ([defaults boolForKey:#"FLS"])
predicate = [NSPredicate predicateWithFormat:#"first_letter_start BEGINSWITH %#", compareString];
else
predicate = [NSPredicate predicateWithFormat:#"first_letter_start CONTAINS %#", compareString];
[request setPredicate:predicate];
[request setFetchLimit:100];
[request setFetchBatchSize:1];
[request setPropertiesToFetch:[NSArray arrayWithObject:#"gurmukhi"]];
[request setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:request error:&error];
for (Gurbani *shabad in fetchedObjects)
{
[retval addObject:shabad.gurmukhi];
}
searchResults = retval;
[searchResultsTable reloadData];
}
return retval;
};
and here's the log after putting in the debug arguments:
2013-01-11 18:04:57.195 GurbaniKhoj[2833:907] CoreData: sql: pragma cache_size=200
2013-01-11 18:04:57.199 GurbaniKhoj[2833:907] CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
2013-01-11 18:05:01.139 GurbaniKhoj[2833:907] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSHABAD t0 WHERE NSCoreDataStringSearch( t0.ZFIRST_LETTER_START, ?, 8, 0) LIMIT 100
2013-01-11 18:05:06.705 GurbaniKhoj[2833:907] CoreData: annotation: sql connection fetch time: 5.5652s
2013-01-11 18:05:06.707 GurbaniKhoj[2833:907] CoreData: annotation: total fetch execution time: 5.5682s for 92 rows.
What's going on? And what can I do to make this go faster?
EDIT: one more thing I noticed is that this speed isn't consistent. for some queries it's 5.5 seconds, for others it's 1.8. I have no idea what's causing the fluctuation.
EDIT 2: Figured out my indexes weren't being created. created a versioned data model, made sure they were indexed, and now This is what the log looks like
2013-01-15 03:06:29.671 GurbaniKhoj[1212:907] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSHABAD t0 WHERE NSCoreDataStringSearch( t0.ZFIRST_LETTER_START, ?, 8, 0) LIMIT 100
2013-01-15 03:06:31.130 GurbaniKhoj[1212:907] CoreData: annotation: sql connection fetch time: 1.4588s
2013-01-15 03:06:31.132 GurbaniKhoj[1212:907] CoreData: annotation: total fetch execution time: 1.4607s for 92 rows.
EDIT 3: Is there any way I can make this faster? I still don't feel like 1.5 seconds is very fast though I'm doing these string comparisons for every single one.
Mukhi,
First, have you done the obvious thing and indexed your field?
Second, iOS devices have very slow fetch times. This isn't technically a flaw with Core Data. (Though CD may also be a performance sucking culprit here.) Hence, whenever possible, you should do large fetches into RAM and refine your search there. For example, when the first char is a 'b', fetch all of those and then refine from that subset in RAM with subsequent typed characters. You'll find that this is quite fast.
Third, the variability in performance you are seeing is probably due to the SQLite row cache and perhaps a filled MOC.
Andrew
Try to change fetch batch size from 1 to 20.
[request setFetchBatchSize:20];
I have an expense tracking iOS Application using Core Data Model:
Scenario:
-> An abstract parent entity named "Money" with attributes "vendor", "date", "amount" and so on. The two sub-entities "Expense" and "Income" inherit from the parent entity "Money". Then there are other entities such as "Category" and "subCategory" with their attributes. Total as of now: 5 entities (Money, Expense, Income, Category and Subcategory) in my data model.
Question: What I want to achieve is to track expenses daily, per week, bi-weekly, monthly and yearly. I am thinking to make an entity say "Months" with 12 attributes ( Jan - Dec - > month names) but isn't that too much complicated?
Thoughts?
I have a Table-view and using NSFetchedResultsController to fill my table-view with the mix od expenses and Incomes.
You have to create an NSFetchRequest with an NSPredicate that filters for the appropriate time period. Let's say you want to list all expenses between two dates your could implement a method that returns an NSFetchRequest that filters for this period:
- (NSFetchRequest *)filteredFetchRequestForEntity:(NSString *)entityName startDate:(NSDate *)startDate endDate:(NSDate *)endDate
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#", startDate, endDate];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
return fetchRequest;
}
You could then use this fetchRequest for example in an NSFetchedResultsController
similar to my answer in your other question:
CoreData and TableViews
Then all you'd have to do is create the NSDate objects for start- and endDate for your desired time period and pass them to this method.
Setup:
I have a Core Data object A that has a to-many relation to B. Call the relation "items". So, a.items returns all B-s associated with A.
Now, I have a manually composed NSSet "itemSet" of B objects.
I want to do the following:
return all A objects whose "items" relation exactly matches itemSet
How do I construct a predicate for that? I’ve tried this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(ALL items in %#)", itemSet];
But that just gives me Unsupported predicate (null).
This:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(items in %#)", itemSet];
tells me unimplemented SQL generation for predicate. Interesting, but not helpful.
So what’s the right way to filter the relation with a set?
The following predicate could work:
[NSPredicate predicateWithFormat:#"(items.#count == %d) AND (SUBQUERY(items, $x, $x IN %#).#count == %d)",
itemSet.count, itemSet, itemSet.count];
The predicate checks first that the number of items is equal to the size of the given itemSet, and then checks that the number of items which are member of itemSet is also equal to the size of itemSet. If both are true, then items must be equal to itemSet.
Have you tried:
NSPredicate *predicate = [NSPredicate predicateWithFormate:#"items == %#", itemSet];
Alternatively, pull out a subset with a simpler predicate and filter them outside of the fetch request. i.e.
Set a predicate for the number of items in the relationship to the be the same as the number of items in your comparison set.
Fetch the results
Filter these results to show only the ones where the sets contain the same items.