sqlite3 and fmdb nested FMResultSet is possible? - iphone

I'm trying to iterator through a master detail sort of tables and I'd like to populate the master/detail structures as I go. Apparently when I nest result sets I get a BAD Access exception:
FMDatabase *db = self.database;
[db open];
db.traceExecution = YES;
db.logsErrors = YES;
FMResultSet *rs = [db executeQuery:#"select group_id, label from main.preference_group order by group_id"];
while ([rs next])
{
PreferenceGroup *pg = [[PreferenceGroup alloc] init];
pg.group_id = [rs intForColumn:#"group_id"];
pg.label = [rs stringForColumn:#"label"];
pg.translatedLabel = NSLocalizedString(pg.label, nil);
NSMutableArray * prefs = [[NSMutableArray alloc] init];
[prefGroups addObject:prefs];
FMResultSet *rs2 = [db executeQuery:#"select pref_id, label, value from main.preference where group_id = ? order by pref_id", pg.group_id, nil];
while ([rs2 next])
{
Preference * pref = [[Preference alloc] init];
pref.group_id = pg.group_id;
pref.pref_id = [rs2 intForColumn:#"pref_id"];
pref.label = [rs2 stringForColumn:#"label"];
pref.value = [rs2 stringForColumn:#"value"];
pref.translatedLabel = NSLocalizedString(pref.value, nil);
[prefs addObject:pref];
}
[rs2 close];
}
[rs close];
[db close];
In the rs2 (second result set) I get the EXEC_BAD_ACCESS within FMDatabase class.
Is this a restriction of sqlite3/fmdb or am I doing something wrong here?

I just found what I did wrong. I was passing a int as part of the second query. I had to convert it to NSNumber:
FMResultSet *rs2 = [db executeQuery:#"select pref_id, label, value from main.preference where group_id = ? order by pref_id", [NSNumber numberWithInt:pg.group_id], nil];
So that means, YES, sqlite3/fmdb does support nested queries! :-)

I'm using FMDB and SQLITE3 as well, and I find nested queries work:
(I'm not claiming the code hint below is good, never mind the format, please)
Both Master and Detail tables have a column called 'id'
FMResultSet *rso = [database executeQuery:#"select * from master order by id"];
while ([rso next])
{
NSInteger masterId = [rso intForColumn:#"id"];
NSString *q3 = [[NSString alloc] initWithFormat:
#"select * from detail where masterid = %d order by id", masterId, nil ];
FMResultSet *rsa = [database executeQuery:q3 ];
while ([rsa next])
{
NSInteger detailId = [rsa intForColumn:#"id"];
//
// here do something with masterId and detailId
}
}
This was a pleasant surprise, actually. I was halfways expecting to have to first query all master records, and then loop through them in App memory to pick up the details from SQlite3.
But the above construct works fine.

Related

Issue with passing variables to FMDB

In my app I'm using FMDB to query my sqlite database. I'm passing variables from one view to another and at the end all the selections the variables are filled with the values selected. On the results page I can show these values in label's fine. But the moment I pass them to FMDB to query the database I don't get any values returned. It crashes and says that the array is 0 which I know it's not.
Code sample below.
- (NSMutableArray *) getChoices
{
NSMutableArray *choices = [[NSMutableArray alloc] init];
FMDatabase *db = [FMDatabase databaseWithPath:[Utility getDatabasePath]];
[db open];
NSString *capsChoiceOne = #"CHOICE 1";
NSString *capsChoiceTwo = #"CHOICE 2";
NSString *capsChoiceThree = #"CHOICE 3";
NSString *capsChoiceFour = #"CHOICE 4";
FMResultSet *results = [db executeQueryWithFormat:#"SELECT * FROM allitems WHERE choice1=%# AND choice2=%# AND choice3=%# AND choice4=%#"
,capsChoiceOne,capsChoiceTwo,capsChoiceThree,capsChoiceFour];
while([results next])
{
Choices *choice = [[Choices alloc] init];
choice.result = [results stringForColumn:#"result"];
[choices addObject:choice];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Your Result"
message:choice.result
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
[db close];
return choices;
Now the above code will bring back the result in the Alert View. But the moment I change one of the values to a variable it crashes and say's there was 0 results in my array. I've put the code below for how I'm inserting the variable.
NSString *capsChoiceOne = self.choiceOneSelected.uppercaseString;
the self.choiceOneSelected.uppercaseString contains the same as the hard coded version but doesn't work.
Any help would be grateful.
Thank you
Check whether self.choiceOneSelected.uppercaseString; is not nil.
Also your query have some issues, you need to wrap string values inside '
So use:
FMResultSet *results = [db executeQueryWithFormat:#"SELECT * FROM allitems WHERE choice1='%#' AND choice2='%#' AND choice3='%#' AND choice4='%#'"
,capsChoiceOne,capsChoiceTwo,capsChoiceThree,capsChoiceFour];

Reading from SQLite - FMDB - Beginner

I am trying to read from a database file (performing a simple select all functions).
I am using FMDB.
Here's how i created the DB;
Pro:~ dd$ sqlite3 db.db
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table cus(id integer primary key, firstname varchar(30));
sqlite> inser into cus(firstname)values('f');
Error: near "inser": syntax error
sqlite> insert into cus(firstname)values('f');
sqlite> select * from cus;
1|f
sqlite>
I copied the file (db.db) to my resource folder in xCode. changed the name of the db file to db.db in the app delegate. Code for my program is exactly yhe same as this tutorial.
Here's the code ;
-(NSMutableArray *) getCustomers
{
NSMutableArray *customers = [[NSMutableArray alloc] init];
NSString * path = [(AppDelegate*)[[UIApplication sharedApplication]delegate]databasePath];
NSLog(#"DB path %# ",path);
FMDatabase *db = [FMDatabase databaseWithPath:path];
[db open];
FMResultSet *results = [db executeQuery:#"SELECT * FROM cus"];
NSLog(#"result %# ",results);
while([results next])
{
NSLog(#"result %# ",results);
Customer *customer = [[Customer alloc] init];
customer.customerId = [results intForColumn:#"id"];
customer.firstName = [results stringForColumn:#"firstname"];
[customers addObject:customer];
}
[db close];
return customers;
}
My problem;
Eventhough there is 1 record in the DB, the result of the Select statement is NULL. Why is this and how can i correct it ?
Assuming that the database was created and imported into the project successfully, try the following:
-(NSMutableArray *) getCustomers
{
NSMutableArray *customers = [[NSMutableArray alloc] init];
NSString * path = [(AppDelegate*)[[UIApplication sharedApplication]delegate]databasePath];
NSLog(#"DB path %# ",path);
FMDatabase *db = [FMDatabase databaseWithPath:path];
if(![db open])
{
NSLog(#"Could not open DB, try again");
return nil;
}
FMResultSet *results = nil;
results = [db executeQuery:#"SELECT * FROM cus"];
NSLog(#"result %# ",results);
while([results next])
{
Customer *customer = [[Customer alloc] init];
customer.customerId = [results intForColumn:#"id"];
customer.firstName = [results stringForColumn:#"firstname"];
NSLog(#"Customer object %#", customer);
[customers addObject:customer];
[customer release];
}
[db close];
return customers;
}
I have had this same problem but was managed to resolve this by setting up the path correctly. So, there could be something wrong in the path specification. Do make sure that your database path is perfect. And as everyone suggests, I recommend you to use error statements to narrow down the issue. Wishes!!

AWS SimpleDB Select Expression iPhone

I need to construct a selectExpression for SimpleDB where I only want items with attributes = to X, sorted in descending order by attribute Y. All I have so far is this:
NSString *selectExpression = [NSString stringWithFormat:#"select itemName() from `%#`",domainString];
#try {
SimpleDBSelectRequest *selectRequest2 = [[[SimpleDBSelectRequest alloc] initWithSelectExpression:selectExpression] autorelease];
SimpleDBSelectResponse *selectResponse = [[Constants sdb] select:selectRequest2];
if (items == nil) {
items = [[NSMutableArray alloc] initWithCapacity:[selectResponse.items count]];
}
else {
[items removeAllObjects];
}
for (SimpleDBItem *item in selectResponse.items) {
[items addObject:item.name];
}
[items sortUsingSelector:#selector(compare:)];
}
#catch (AmazonServiceException *exception) {
NSLog(#"Exception = %#", exception);
}
I would prefer to only select items (above) with attributes X by Y rather than get all items and then sort. How do I add this? Does it go in the selectExpression string or somewhere else. Many thanks!
Try this out --
Select x,y from <domain_name> where itemName() is not null order by itemName() asc
NSString *selectExpression = [NSString stringWithFormat:#"select itemName() from `%#` where X = '%#' order by 'X' descending",domainName,attributeValue];

SQLite Changes Not Saved

I'm using SQLite in iOS 4 on an iPhone, but the changes made by my update statements aren't saved. At first thought perhaps quitting the app might be deleting the database somehow, but they're not even persisted during the same session. The code to initialize my database is (using FMDB):
-(SQLiteDal*) init {
pool = [[NSAutoreleasePool alloc] init];
self = [super init];
if(self != nil){
// Setup some globals
NSString *databaseName = [self getDbPath];
NSLog([NSString stringWithFormat:#"db path: %#", databaseName]);
db = (FMDatabase *)[FMDatabase databaseWithPath:databaseName];
if (![db open]) {
NSLog(#"Could not open db.");
[pool release];
return 0;
}
}
//[self checkAndCreateDatabase];
return self;
}
#pragma mark DB Maintenance
-(NSString *)getDbPath {
NSString *databaseName = #"myapp.db";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databaseName = [documentsDir stringByAppendingPathComponent:databaseName];
return databaseName;
}
Both of these methods are called to create the database, then to insert to a table I call:
[db executeQuery:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES (?,?,?,?,?)",
f.Name,
f.description,
f.area,
f.price,
f.id];
The problem is, when I come to read from MyTable using the statement below, I never get anything back:
FMResultSet *rs = [db executeQuery:#"SELECT * FROM MyTable WHERE ID = ?", id];
while ([rs next]) {
//.. this is never called
As far as I can see I'm not missing anything out, and the DB seems to be in a writable location.
When inserting you need to call executeUpdate not executeQuery. Also you should call beginTransaction and then commit, like this:
[_dbPointer beginTransaction];
BOOL isInserted = [_dbPointer executeUpdate:[NSString stringWithFormat:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES(?,?,?,?,?);", f.Name,
f.description,
f.area,
f.price,
f.id]];
[_dbPointer commit];

SQL select statement equivalent for query on plist

I am trying to figure out the best approach for queries on a plist. Thus, attempting at making an sql equivalent to "SELECT * FROM sometable WHERE somecol = someval AND someothercol = someotherval AND ".... so on and soforth ... using a plist as the sql table equivalent. Coming from Ruby and mysql, this just seems like a lot of code for one simply query. The results come back as expected (at least at first run they did, I have not tested this rigorously) with no errors.
So here is the question: Is there some simple method hiding in the docs somewhere that would make this less clunky?
and if not what is a better approach?
EPFramework.m
// LOAD PLIST AND FILTER MULTIPLE TIMES
-(NSMutableArray *)loadPlistAndFilterMultipleTimes:(NSString *)plist ArrayOfKeys:(NSArray *)arrayOfKeys ArrayOfKeyValues:(NSArray *)arrayOfKeyValues
{
// set the array counts
int arrayOfKeysCount = [arrayOfKeys count];
int arrayOfKeyValuesCount = [arrayOfKeyValues count];
// initialize the array to return
NSMutableArray *arrayFilteredResults = [[[NSMutableArray alloc] init] retain];
// qualify the search
if(arrayOfKeysCount == arrayOfKeyValuesCount && arrayOfKeysCount > 0 && arrayOfKeyValuesCount > 0)
{
// get the plist
NSString *fullFileName = [NSString stringWithFormat:#"%#.plist", plist];
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fullFileName];
// put the plist records into an array
NSArray *arrayOfDictionaryItemsInPlist = [[NSMutableArray alloc] initWithContentsOfFile:path];
// load our dynamic array for mutability throughout the loops
NSMutableArray *arrayFiltered = [[[NSMutableArray alloc] initWithArray:arrayOfDictionaryItemsInPlist] retain];
// build an array of the final results to return
for(int i=0; i < arrayOfKeysCount; i ++)
{
// initialize this loops search criteria
NSString *key = [arrayOfKeys objectAtIndex:i];
id value = [arrayOfKeyValues objectAtIndex:i];
// set the filter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", key, value];
// filter the result
arrayFilteredResults = [[[NSArray alloc] initWithArray:[arrayFiltered filteredArrayUsingPredicate:predicate]] retain];
}
} else {
NSLog(#"arrOfKeys count does not match arrayOfKeyValues count"); // the search did not qualify
}
// return the results
return arrayFilteredResults;
// release the allocated memory
[arrayFilteredResults release];
}
IndexController.m
NSArray *arrayOfKeys = [NSArray arrayWithObjects:
[NSString stringWithString:#"recordset"],
[NSString stringWithString:#"ep_object_attribute_id"],
nil];
NSArray *arrayOfKeyValues = [NSArray arrayWithObjects:
[NSString stringWithString:#"1778587279"],
[NSNumber numberWithInt:133],
nil];
NSMutableArray *arrayOfResult = [epFrameWork loadPlistAndFilterMultipleTimes:#"FormEntries" ArrayOfKeys:arrayOfKeys ArrayOfKeyValues:arrayOfKeyValues];
NSLog(#"arrayOfResult: %#", arrayOfResult);
Here's the link for fmdb. Should be quick to pick it up, be faster and you get a real database instead of simulating one with plists :)
https://github.com/ccgus/fmdb
Hope that helps