I'm trying to get allways the same order for my fetch data, because all the time I request, he get another sorted array so I'm trying to order by ID but I couldn't.
My situation:
I have 2 models, FORM and FORMCOMPONENT
1 FORM have many FORMCOMPONENT
when I do this:
Form *form = ...;
[form formComponents]; //I get all the components but each time I run I get with a differente order
How I suppose order this if I don't have any field for ORDER? On android I did this with the ID.. that's why I don't have any field with this proposal.
I tryed to order in a array with sortWithComparator but I can't do this because I'm not allowed to get the number ID (only the number, not the entire string he give me with [obj objectID] ). This is funny because when I use sqlite3 for see the database only have the number there.
Another way I thought about is get the FORMCOMPONENTs direct with a new request, not by FORM one ([form formComponents]), like this:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FormComponent" inManagedObjectContext:[appDelegate managedObjectContext]];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
//SOME PREDICATE WITH THE FORM
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *arrayComps = (NSMutableArray*) [[appDelegate managedObjectContext] executeFetchRequest:request error:&error];
so, anyone have an idea? Thanks.
--- EDIT ---
PS: I don't want to avoid the problem creating another field for order. If I don't have any other solution so I will do this.
You can sort by objectID by using a sort descriptor with self as the sort key. The underlying SQL will have ORDER BY t0.Z_PK, Z_PK being the internal ID of the record (as found as the last component of the object's NSManagedObjectID).
Core data is no database.
Internally Core Data uses PK, of course. But Core Data is no database, it is a system to model a graph. Consequently no "ID information" is published. Looking for an ID is anti-conceptional.
If your data does not have a "natural order", there should be nothing wrong in getting a random order. If it has a natural order, simply use sort descriptors.
If you need an order "creation date" simply add a property creationDate and set it in -awakeFromInsert. (BTW: AFAIK it is an urban legend, that PK always raises. A PK can be something else than autoincrement.)
Core Data doesn't expose any kind of SQL-style numeric ID that you could use for sorting. If you don't want to add an attribute to sort on, the easiest thing might be to configure this relationship as being ordered. Then you get an NSOrderedSet for the relationship, and you can keep individual instances in whatever order you want. Make the relationship ordered in the Core Data model editor, and use mutableOrderedSetValueForKey: when you want to add a new instance to the relationship.
The recommended way it to add a field for the ID/sort order you want to give to your components.
Alternatively you could make the relationship ordered by checking the checkbox for that in the CoreData editor. This has some more overhead, but is the only option if components need to appear in multiple lists.
Meddling with SQLite directly isn't a smart approach.
Thanks for the help. I found what I'm looking for (with this I can order for example)
I need to get PK direct from sql, if anyone needs do this, here is the code:
Just don't forget what our friend Tom said: "That's not how Core Data works"
-(NSMutableArray*) readDatabase {
NSLog(#"READ");
sqlite3 *database;
NSMutableArray *components = [[NSMutableArray alloc] init];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *databasePath = [documentsDir stringByAppendingPathComponent:#"Model.sqlite"];
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
NSLog(#"SQLITE_OK");
// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "select * from zformcomponent";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Read the data from the result row
int aPK = sqlite3_column_int(compiledStatement, 0);
//NSString *aDescription = [NSString stringWithUTF8String: (char *)sqlite3_column_text(compiledStatement, 2)];
//NSString *aImageUrl = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
NSLog(#"PK: %d",aPK);
//Animal *animal = [[Animal alloc] initWithName:aName description:aDescription url:aImageUrl];
//[animals addObject:animal];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return components;
}
Related
I am working on a project that is built on raw sqlite, and I have changed it to FMDB.
All the queries are parametrized.
Here is one such an example:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName='%#' AND LastName='%#'", fName,lName];
and I pass it to my helper class:
NSInteger count = [[[DB sharedManager] executeSQL:sqlQuery] integerValue];
Helper class:
- (NSString*)executeSQL:(NSString *)sql
{
__block NSString *resultString = #"";
[_secureQueue inDatabase:^(FMDatabase *db) {
FMResultSet *results = [db executeQuery:sql];
while([results next]) {
resultString= [results stringForColumnIndex:0];
}
}];
return resultString;
}
I can make a workaround something like:
sql = #"SELECT COUNT(*) FROM Contacts WHERE FirstName=? AND LastName=?"
[db executeQuery:sql,firstParam, secondParam]
But I do not want to change the helper method, I need to pass the changed/updated sql query to my helper method.
How can I change this:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName='%#' AND LastName='%#'", fName,lName];
to something like:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName=? AND LastName=?", fName,lName];
If you want to avoid SQL Injection problems, then you must never build a query using stringWithFormat. You must properly bind variables into the query. Period. End of discussion.
So you have no choice but to change your helper. Have it take two parameters instead of one. The first being the query with the proper use of ? and the second being an array of values that get bound into the query but the helper method.
I have two UISlider's that represent minimum and maximum prices of items. I am passing these as well as other various data back to the previous controller.
I've used a protocol method and set the previous controller as a delegate to make it possible to pass values back to the controller.
I can easily grab the other objects out of the array because they're strings. I just use:
[_finalSelectionForRefinement containsObject:#"size"];
This is what I do in pushed controller:
// create dictionary with keys minimum and maximum that hold the
// position of the slider as a float
_dictionaryWithSliderValues =
[[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:_minSliderPosition], #"minimum",
[NSNumber numberWithFloat:_maxSliderPosition], #"maximum", nil];
// store this in the array that is retrieved in previous controller
[_finalSelectionForRefinement addObject:_dictionaryWithSliderValues];
My question is how do I now use the minimum and maximum keys to grab the slider position float objects?
Thought I could use NSPredicate but the examples I've been coming across on blogs as well as youTube are of no help to my specific needs.
Would appreciate some help here
Regards
UPDATE - Short snipped of method in previous controller where I need to retrieve the slider minimum and maximum values:
-(PFQuery *)queryForCollection
{
PFQuery *query = [PFQuery queryWithClassName:#"Garments"];
if (_selectedRowInFilterPicker == 0) {
NSLog(#"ORDER BY RECOMMENDED");
[query orderByDescending:#"recommended"];
} else if (_selectedRowInFilterPicker == 1) {
NSLog(#"ORDER BY NEWEST ITEMS");
[query orderByDescending:#"createdAt"];
} else if (_selectedRowInFilterPicker == 2) {
NSLog(#"ORDER BY DESCENDING USING PRICE");
[query orderByDescending:#"price"];
} else if (_selectedRowInFilterPicker == 3) {
NSLog(#"ORDER BY ASCENDING USING PRICE");
[query orderByAscending:#"price"];
}
if ([_selectionFromRefineResultsController count] > 0) {
NSLog(#"Selection FROM REF MADE");
// Gender
if ([_selectionFromRefineResultsController containsObject:#"Male"]) {
[query whereKey:#"gender" equalTo:#1];
}
if ([_selectionFromRefineResultsController containsObject:#"Female"]) {
[query whereKey:#"gender" equalTo:#2];
}
}
// Here I need to check there is a minimum or maximum value in the array
// If there is I can user [query whereKey:#"price" greaterThan:MINVAL] and MAXVAL
// This will return items within the correct price range.
This queryForCollection method is called from within another method called performQuery which is called when the button of the second controller is tapped to pass data back to the controller that pushed it in the first place.
You should look at the documentation for NSMutableArray , NSArray and NSDictionary
Which will explain the instance methods for each.
But in a nutshell any object that you add should be in a NSDictionary so it has a value and a key. This includes any of your strings. Doing so simplifies how you search by using keys.
If the NSMutableArray contains objects that are not KVC then you will I think find it harder it go through the objects in one sweep.
Because NSmutableArray inherites from NSArray you can then use instance method valueForKey: on a NSmutableArray whose objects values or objects objects values have keys.
valueForKey:
Returns an array containing the results of invoking
valueForKey: using key on each of the array's objects.
(id)valueForKey:(NSString *)key
Rough Example:
NSMutableArray * finalSelectionForRefinement =[[NSMutableArray alloc]init];
NSDictionary *dictionaryWithSliderValues = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:10], #"minimum", [NSNumber numberWithFloat:20], #"maximum", nil];
NSDictionary *stringValues = [[NSDictionary alloc] initWithObjectsAndKeys:#"the-size", #"size", #"the-hight", #"hight", nil];
[finalSelectionForRefinement addObject:dictionaryWithSliderValues];
[finalSelectionForRefinement addObject:stringValues];
NSLog(#"finalSelectionForRefinement = %#", [finalSelectionForRefinement valueForKey:#"maximum"] );
First off, you can of store everything in one NSDictionary which makes more sense. But I wanted to show you that the valueForKey: will search within each.
The other thing is valueForKey: will return an NSArray containing the results. any objects that it finds that do not match the key you are looking for will be returned as an NSNull object. i.e
finalSelectionForRefinement = (
20,
"<null>"
)
So you would need to still single your value out. One way is use a objectEnumerator like this:
NSEnumerator *enumerator = [[finalSelectionForRefinement valueForKey:#"maximum"] objectEnumerator];
id anObject;
while (anObject = [enumerator nextObject]) {
if(![anObject isKindOfClass:[NSNull class]])
{
NSLog(#"anObject = %#", anObject);
}
}
Which should return:
anObject = 20
There are most likely better ways of doing this. All of the above is just to give you one idea. And I suspect you could cut out a lot of the code by using bindings.
(also note this answer was being constructed before you question update)
I am adding functionality to delete a song (playlistTrack) from a Playlist. Each playlistTrack has a playlist_track_number associated with it, so we know what order the songs are to be played in. Thus, after deleting a playlistTrack I need to assign all the playlistTracks that follow a new playlist_track_number, namely 1 less than their previous playlist_track_number.
My code here runs fine once and then crashes with the console displaying "'Can't do regex matching on object 3." any help?
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSPredicate *predicateList = [NSPredicate predicateWithFormat:#"(playlist.name CONTAINS[cd] %#)", activePlaylistString];
NSArray *newArray = [[NSArray alloc]init];
newArray = (NSArray*)[Playlist_Track MR_findAllWithPredicate:predicateList];
for (int i = (delTrack + 1); i <= [newArray count]; i++) {
localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSString *nextTrackNumberString = [NSString stringWithFormat:#"%i",i];
NSPredicate *nextPredicate = [NSPredicate predicateWithFormat:#"(playlist.name CONTAINS[cd] %#) AND (playlist_track_number MATCHES [cd] %#)", activePlaylistString, nextTrackNumberString];
nextTrack = [Playlist_Track MR_findFirstWithPredicate:nextPredicate inContext:localContext];
if (nextTrack) {
int j = i-1;
nextTrack.playlist_track_numberValue = j;
[localContext MR_saveToPersistentStoreWithCompletion:nil];
}
if (!nextTrack) {
//Do Nothing
}
}
Instead of doing lots of different fetches, just do one fetch to get all of the objects that need to be updated, then iterate over them and perform the updates. Depending on the number if items you can batch the response from the fetch or return the items as faults and then batch the saves.
This error tell about you trying to use 'NSPredicate' with inappropriate types
(playlist_track_number MATCHES [cd] %#)
I suppose you are trying to MATCH number playlist_track_number with string.
BTW i suppose have a not very efficient architecture in your CoreData.
This delete operation will took a very long time
(find and fetch all than multiple find one in a loop)
Also your current architecture does not allow to use one playlistTrack in 2 or more playlists
So it is good idea to remove playlist_track_number property from playlistTrack
To create fast inserts and removes from playlist you can use linked list or doubly-linked list structure. When each playlistTrack item will have relationship to next (and previous) item.
In playlist entity you can have only relationship to head (and tail) items also good to have count property.
Also you can use NSOrderedSet for one-to-many relationship in CoreData
this will allow you to insert item at index and remove item at index
(But it have a lot of unresolved bugs for current moment)
Both variants will allow you to have track number/order without playlist_track_number property
Using Flop's suggestion, I reworked the code as follows. This is hardcoded to always delete the track at index 3, but that can be changed easily by passing a parameter into the method.
NSArray *trackList = [[NSArray alloc]init];
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
Playlist *selectedPlaylist;
selectedPlaylist = [Playlist MR_findFirstByAttribute:#"name" withValue:activePlaylistString];
trackList = (NSArray*)selectedPlaylist.playlist_trackSet;
NSMutableArray *newArray = [trackList mutableCopy];
NSUInteger delIndex = 3;
[newArray removeObjectAtIndex:delIndex];
trackList = newArray;
NSOrderedSet *trackSet = (NSOrderedSet*)trackList;
selectedPlaylist.playlist_track = trackSet;
[localContext MR_saveToPersistentStoreWithCompletion:nil];
I'm trying to use a datareader in SQLite, but am unable to find anything in the docs I have ("Using SQLite" by Kreibich).
Can someone tell me if it's supported and where I can find some examples?
Yes, you just need to get yourself System.Data.SQLite.
It comes in two variants, one that has SQLite built-in, and another which requires that you also ship a separate native sqlite DLL.
the sqlite api has a concept which is logically equivalent to the .net reader. which means, you issue a query and then iterate read data as needed. that keeps memory low as your not pulling the complete result set into memory.
first of all, take a look at other wrappers like fmdb.
here's the equivalent using the c api inside of iPhone. You prepare the statement by passing the sql query (sqlite parses under the cover), then you call step which is the equivalent to the .net reader read method. The you read columns just like the .net data reader. Note that this example prepares and finalizes (cleans up). A more efficient approach is to save the prepared statement and then call reset to avoid having sqlite parse the query over and over.
// prep statement
sqlite3_stmt *statement;
NSString *querySQL = #"SELECT id, name FROM contacts";
NSLog(#"query: %#", querySQL);
const char *query_stmt = [querySQL UTF8String];
// preparing a query compiles the query so it can be re-used.
sqlite3_prepare_v2(_contactDb, query_stmt, -1, &statement, NULL);
// process result
while (sqlite3_step(statement) == SQLITE_ROW)
{
int idx = 0;
Contact *contact = [[Contact alloc] init];
NSNumber *idField = [NSNumber numberWithLongLong:sqlite3_column_int64(statement, idx++)];
[contact setId:idField];
NSString *nameField = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, idx)];
[contact setName:nameField];
NSLog(#"id: %#", [contact id]);
NSLog(#"name: %#", [contact name]);
[nameField release];
[contactsList addObject:contact];
[contact release];
}
sqlite3_finalize(statement);
When using CoreData, the following multi-column index predicate is very slow - it takes almost 2 seconds for 26,000 records.
Please note both columns are indexed, and I am purposefully doing the query with > and <=, instead of beginswith, to make it fast:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"airportNameUppercase >= %# AND airportNameUppercase < %# \
OR cityUppercase >= %# AND cityUppercase < %# \
upperText, upperTextIncremented,
upperText, upperTextIncremented];
However, if I run two separate fetchRequests, one for each column, and then I merge the results, then each fetchRequest takes just 1-2 hundredths of a second, and merging the lists (which are sorted) takes about 1/10th of a second.
Is this a bug in how CoreData handles multiple indices, or is this expected behavior? The following is my full, optimized code, which works very fast:
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease];
[fetchRequest setFetchBatchSize:15];
// looking up a list of Airports
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Airport"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// sort by uppercase name
NSSortDescriptor *nameSortDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"airportNameUppercase"
ascending:YES
selector:#selector(compare:)] autorelease];
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:nameSortDescriptor, nil]autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
// use > and <= to do a prefix search that ignores locale and unicode,
// because it's very fast
NSString *upperText = [text uppercaseString];
unichar c = [upperText characterAtIndex:[text length]-1];
c++;
NSString *modName = [[upperText substringToIndex:[text length]-1]
stringByAppendingString:[NSString stringWithCharacters:&c length:1]];
// for the first fetch, we look up names and codes
// we'll merge these results with the next fetch for city name
// because looking up by name and city at the same time is slow
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"airportNameUppercase >= %# AND airportNameUppercase < %# \
OR iata == %# \
OR icao == %#",
upperText, modName,
upperText,
upperText,
upperText];
[fetchRequest setPredicate:predicate];
NSArray *nameArray = [context executeFetchRequest:fetchRequest error:nil];
// now that we looked up all airports with names beginning with the prefix
// look up airports with cities beginning with the prefix, so we can merge the lists
predicate = [NSPredicate predicateWithFormat:
#"cityUppercase >= %# AND cityUppercase < %#",
upperText, modName];
[fetchRequest setPredicate:predicate];
NSArray *cityArray = [context executeFetchRequest:fetchRequest error:nil];
// now we merge the arrays
NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:[cityArray count]+[nameArray count]];
int cityIndex = 0;
int nameIndex = 0;
while( cityIndex < [cityArray count]
|| nameIndex < [nameArray count]) {
if (cityIndex >= [cityArray count]) {
[combinedArray addObject:[nameArray objectAtIndex:nameIndex]];
nameIndex++;
} else if (nameIndex >= [nameArray count]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
} else if ([[[cityArray objectAtIndex:cityIndex]airportNameUppercase] isEqualToString:
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
nameIndex++;
} else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] <
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
} else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] >
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]) {
[combinedArray addObject:[nameArray objectAtIndex:nameIndex]];
nameIndex++;
}
}
self.airportList = combinedArray;
CoreData has no affordance for the creation or use of multi-column indices. This means that when you execute the query corresponding to your multi-property predicate, CoreData can only use one index to make the selection. Subsequently it uses the index for one of the property tests, but then SQLite can't use an index to gather matches for the second property, and therefore has to do it all in memory instead of using its on-disk index structure.
That second phase of the select ends up being slow because it has to gather all the results into memory from the disk, then make the comparison and drop results in-memory. So you end up doing potentially more I/O than if you could use a multi-column index.
This is why, if you will be disqualifying a lot of potential results in each column of your predicate, you'll see much faster results by doing what you're doing and making two separate fetches and merging in-memory than you would if you made one fetch.
To answer your question, this behavior isn't unexpected by Apple; it's just an effect of a design decision to not support multi-column indices in CoreData. But you should to file a bug at https://feedbackassistant.apple.com/ requesting support of multi-column indices if you'd like to see that feature in the future.
In the meantime, if you really want to get max database performance on iOS, you could consider using SQLite directly instead of CoreData.
When in doubt, you should file a bug.
There isn't currently any API to instruct Core Data to create a compound index. If a compound index were to exist, it would be used without issue.
Non-indexed columns are not processed entirely in memory. They result in a table scan, which isn't the same thing as loading the entire file (well, unless your file only has 1 table). Table scans on strings tend to be very slow.
SQLite itself is limited in the number of indices it will used per query. Basically just 1, give or take some circumstances.
You should use the [n] flag for this query to do a binary search against normalized text. There is a sample project on ADC called 'DerivedProperty'. It will show how to normalize text so you can use binary collations as opposed to the default ICU integration for fancy localized Unicode aware text comparisons.
There's a much longer discussion about fast string searching in Core Data at https://devforums.apple.com/message/363871