Below is my code, I'm attempting to retrieve data from an sqlite database with airport city names query from a user entered text field and retrieve the ICAO identifier to be presented in a label. It seems the db is loading but it will not query when I select the IBAction button. I think there might be something wrong with my query statement or my database, although I can't list that on here. Any Help would be greatly appreciated.
The Last error I received is: database3[30351:c07] -[ViewController searchICAO:] 1st SQL error 'library routine called out of sequence' (21)
-(NSString*)filePath {
NSArray*paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0]stringByAppendingPathComponent:#"mydatabase.sqlite"];
}
//open database
- (void)viewDidLoad{
[self openDB];
}
-(void)openDB {
if(sqlite3_open([[self filePath]UTF8String], &airportDB) !=SQLITE_OK) {
sqlite3_close(airportDB);
NSAssert(0, #"Databese failed to open");
status.text = #"Database Failed to Open";
}
else if (sqlite3_open([[self filePath]UTF8String], &airportDB) ==SQLITE_OK) {//this line not really needed but was trying everything
NSLog(#"database opened"); //test
status.text = #"Database Opened"; //test
}
}
- (IBAction)searchICAO:(id)sender
{
//[self.delegate detailViewControllerDidFinish:self]; //for later use
//Get airport name from the text field user enters
NSString*sql = [NSString stringWithFormat:#"SELECT * FROM airports WHERE city=\"%#\"", [searchDB text]];
const char *query_stmt = [sql UTF8String];
sqlite3_stmt *statement;
NSLog(#"%s 1st SQL error '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(airportDB), sqlite3_errcode(airportDB)); //Error Test
This is where I seem to be having problems at...
if (sqlite3_prepare_v2(airportDB, query_stmt, -1, &statement, NULL)==SQLITE_OK) {// Problem is from here, can't get past this point
NSLog(#"%s 2nd SQL error '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(airportDB), sqlite3_errcode(airportDB)); //Error Test
if (sqlite3_step(statement)==SQLITE_ROW) {
status.text = #""; //Clear the status line
NSString *returnICAO = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
status.text = returnICAO; //Insert Airport ICAO letters from the database table
}
sqlite3_finalize(statement);
[super viewDidLoad];
}
sqlite3_close(airportDB);
}
I am not seeing exactly what is going wrong, but I would offer that, unless you have a specific objective that cannot be met by using third-party code, you should consider using FMDB https://github.com/ccgus/fmdb - assuming that you need to go directly to SQLite. I have used it quite a bit and had good success with it.
It's a little strange to be "closing" the DB and calling [super viewDidLoad] from searchICAO:. Was this deliberate? It seems like this might be the source of your problem? On the second query, the DB will be closed?
Related
UPDATED QUESTION, check the end of the post
I am making an iPhone app and I want when the user first lunches the app, for the app to create a new database and populate it with the data of a plist file.
Here is my view did load code:
//get database path
NSString *symptomDatabasePath = [self symptomsDatabasePath];
//check if database exists and initialize if it doesn't
if(![[NSFileManager defaultManager] fileExistsAtPath:symptomDatabasePath])
{
[self createAndPopulateDatabase];
}
My symptomDatabasePath function:
//get the path of the symptoms database
- (NSString *) symptomsDatabasePath
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [pathsArray objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"symptoms.sqlite"];
}
And finally my createANdPopulateDatabase function:
- (void) createAndPopulateDatabase
{
//get query to insert symptoms into database
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [pathsArray objectAtIndex:0];
NSString *databaseDataFile = [documentsDirectory stringByAppendingPathComponent:#"symptomsDatabaseList.plist"];
NSString *symptomData = [NSString stringWithContentsOfFile:databaseDataFile encoding:NSUTF8StringEncoding error:NULL];
sqlite3 *database;
if (sqlite3_open([[self symptomsDatabasePath] UTF8String], &database)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Failed to open database");
}
NSString *createTableQuery = #"CREATE TABLE IF NOT EXISTS tbl_symptoms (symptomCode varchar(15) NOT NULL PRIMARY KEY,title varchar(255) NOT NULL,shortTitle varchar(255) NOT NULL,inclusions varchar(255) NOT NULL,exclusions varchar(255) NOT NULL,symptomCategory varchar(255) NOT NULL);";
char *errorMsg;
//create new table
if(sqlite3_exec(database, [createTableQuery UTF8String], NULL, NULL, &errorMsg)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Error creating table: %s", errorMsg);
}
NSString *insertQueryString = #"INSERT INTO tbl_symptoms (symptomCode, title, shortTitle, inclusions, exclusions, symptomCategory) VALUES ";
//array containing the seperate symptom data
NSArray *symptomArray = [symptomData componentsSeparatedByString:#"),"];
sqlite3_stmt *statement;
//nsmutable array with all queries
NSMutableArray *queryArray = [[NSMutableArray alloc] init];
//loop through the aray with symptoms build queries and store them in an array
for(int i=0; i<[symptomArray count]; i++)
{
//create query string with insert command
NSMutableString *insertString = [[NSMutableString alloc] initWithString:insertQueryString];
//append the specific symptoms data
[insertString appendString:[symptomArray objectAtIndex:i]];
[insertString appendString:#");"];
[queryArray insertObject:insertString atIndex:i];
}
//insert data into database
for(int i=0; i<[queryArray count]; i++)
{
const char *insertChar = [[queryArray objectAtIndex:i] UTF8String];
if(sqlite3_prepare_v2(database, insertChar, -1, &statement, nil)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Error filling table: %s", errorMsg);
}
if (sqlite3_step(statement)!=SQLITE_DONE)
{
NSAssert(0, #"Error inserting data into database: %s", errorMsg);
}
//finalize changes
}
sqlite3_finalize(statement);
sqlite3_close(database);
}
My code is a bit messy because i have been trying various solutions. Anyway I have the data I want to input written like this in my phlist file:
('Z27', 'Fear of a social problem', 'Fear of a social problem', 'concern about/fear of having a social problem', 'if the patient has a social problem, code the problem ', 'Social problems’),
('Z28', 'Limited function/disability (Z)', 'Limited function/disability (Z)', '', '', 'Social problems’),
('Z29', 'Social problem NOS', 'Social problem NOS', 'environmental problems; malingering', '', 'Social problems’
there are 320 such entries in my file, these are the last 3 because they are the ones I am having trouble with.
The app copies all the other 317 entries, but not these 3. And I know it is missing a ) at the end, but that's because my code appends it on every loop.
I get a SIGABRT error and the following error code:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error filling table: (null)'"
Can anyone help me? I probably have done something wrong with sqlite3, though I don't understand why it only doesn't copy the last 3 entries.
Also earlier it wouldn't copy only the last entry, but after I copy pasted it around to see if it was a problem with the entry itself, it started not copying the last 3 :S.
Any help will be greatly appreciated
UPDATE
So it turns out I was getting the errors cause I was using NSAssert. Now I am using NSLogs to log my errors, and the app launches correctly, but I still get the error when copying the final 3 entries.
According to my logs moth the sqlite3_prepare_v2 and the sqlite3_step functions fail, but ONLY for the final 3 entries :S
The problem is that you have smart quotes. For example, I see you have
..., 'Social problems’), ...
Note, you're starting that with a standard apostrophe, but finishing it with a closing single quote. If you look very carefully, they look different.
Replace those smart quotes with standard apostrophe and that should fix the immediate problem.
I'd suggest you log the SQL and carefully examine it. Also, when the sqlite3_prepare_v2 fails, you are not logging the appropriate error message. You can do something like:
if (sqlite3_prepare_v2(database, insertChar, -1, &statement, nil)!=SQLITE_OK) {
NSLog(#"prepare failure: %s", sqlite3_errmsg(database));
sqlite3_close(database);
NSAssert(0, #"Error filling table");
}
I need to develop a sql statement based on values picked on a UIPickerView. If you need a visual idea, here's a link to the screenshot (sorry not enough reputation to post pics yet) . I haven't been able to find any documentation on this and want to make sure I'm on the right track before I dig into it.
Each component (kTypeComponent, kDifficultyComponent, kDescriptionComponent) has three rows to select from (ex. kTypeComponent row1=bike, row2=run, row3=swim)
My thought would be that the sql statement would look something like this
sqlite3_stmt *pickerStatement;
//This would give back a string of the row selected (i.e bike, run, swim)
NSInteger getTypeSelected = [pickerView selectedRowInComponent:kTypeComponent];
NSString typeSQL = [rowOneItems objectAtIndex:getTypeSelected];
const char *pickerSQL = "SELECT description FROM workoutTbl WHERE (type = typeSQL) AND ...
Is this possible to do with a sql statement? I'm only familiar with basic SQL, so I'm not sure
Would the SQL statement go in the action (button) or where I set up my NSMutableArray and open the database? Should it go into a different class?
Edit - Solution
In case anyone comes around with the same problem, here is the solution to it
- (NSArray *)getWorkoutListwithType:(NSString *)workoutType withDifficulty:(NSString *)difficulty withLength:(NSString *)length {
NSMutableArray *workouts;
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"workoutList.sqlite"];
// NSLog(#"Db path is %#",dbPath);
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if (!success){
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if (!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)) {
NSLog(#"error with message '%s'.", sqlite3_errmsg(db));
}
// only alloc/init the array if the SQL database opens properly
workouts = [[NSMutableArray alloc] init];
sqlite3_stmt *sqlStatement;
// add "%%" as a wildcard so the query will say "difficulty LIKE '>30%' and match >30 MINS, >30 HOURS, etc.
NSString *sqlString = [NSString stringWithFormat: #"SELECT description FROM workoutTbl WHERE type LIKE '%#%%' AND difficulty LIKE '%#%%' AND duration LIKE '%#%%'", workoutType, difficulty, length];
NSLog(#"query: %#", sqlString);
const char *sql = [sqlString UTF8String];
if (sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK) {
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
}
while (sqlite3_step(sqlStatement)==SQLITE_ROW) {
[workouts addObject:[NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStatement,0)]];
}
sqlite3_finalize(sqlStatement);
}
#catch (NSException *exception) {
NSLog(#"An exception occured: %#", [exception reason]);
}
#finally {
sqlite3_close(db);
}
// Pass back an immutable copy of the array. if the array is nil, then the database never opened and there will be an error
return [workouts copy];
}
What do you mean by 'three rows to select'? Do you mean 'three fields (columns) to select'? If you want to specify field values, then a statement should like
NSString* sqlStatement = [NSString stringWithFormat:#"SELECT * FROM workoutTbl WHERE type = '%#' AND id = '%i'", typeSQL,idNumber];
I am trying to read data from a sqlite database in an ios app. When I run the app, it is able to open the database but in the log file it shows the message - "Problem with the prepare statement". I don't know what is wrong with my prepare statement Here's my code -
-(NSString *)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFilename];
}
In the viewDidLoad I have -
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myarray = [[NSMutableArray alloc]init];
sqlite3 *database;
if(sqlite3_open([[self dataFilePath]UTF8String], &database)!=SQLITE_OK){
sqlite3_close(database);
NSAssert(0, #"Failed to open database");
}
const char *createSQL = #"SELECT ID, TITLE FROM FIRST ORDER BY TITLE;"; //first is the table in the database
sqlite3_stmt *sqlStmt;
if(sqlite3_prepare_v2(database, [createSQL UTF8String], -1, &sqlStmt, nil)!=SQLITE_OK){
NSLog(#"Problem with prepare statement"); //this is where the code gets stuck and I don't know why
}else{
while(sqlite3_step(sqlStmt)==SQLITE_ROW){
NSInteger number = sqlite3_column_int(sqlStmt, 0);
NSString *title = [NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStmt, 1)];
[myarray addObject:title];
}
sqlite3_finalize(sqlStmt);
}
sqlite3_close(database);
}
If your prepare statement fails, rather than just reporting "Problem with prepare statement", try retrieving the error message, e.g.,
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(database), sqlite3_errcode(database));
This might give you a better indication of the problem.
A problem I've seen in the past is that the database might not be found (because it wasn't included in the bundle, typo in the name, etc.) but the standard sqlite3_open function will create it if it's not there, and thus the sqlite3_open will succeed, but the table in question won't be found in the blank, newly created database. Better than sqlite3_open would be:
sqlite3 *database;
if (sqlite3_open_v2([[self dataFilePath] UTF8String], &database, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK) {
sqlite3_close(database); // not sure you need to close if the open failed
NSAssert(0, #"Failed to open database");
}
That way you get a warning if the database is not found. But if you've done sqlite3_open already, you might have a blank database, so you might want to reset your simulator and/or remove the app from your device, before trying it with sqlite3_open_v2.
Several things you can try.
Clean your app, Remove from simulator or device & try installing a fresh copy again and see if it works.
Open your DB in Terminal & try to run your sql statement at there. Check wether you are getting desired output.
Try changing the nil to NULL. Also, try defining the SQL statement as a const char.
....
const char *sql = "SELECT ID, TITLE FROM FIRST ORDER BY TITLE";
sqlite3_stmt *sqlStmt;
if(sqlite3_prepare_v2(database, sql, -1, &sqlStmt, NULL)!=SQLITE_OK){
....
I have an application where I access an SQLite database several times.. But, once I've accessed the database one time, all following attempts cause the app to crash...
I'm not sure if it's because the database has not been properly released...
An example, I run a search to populate a tableview with names of artists. Once I select an artist, I'm navigated to a new tableview, where I want to populate it with the artist's works.
But here's the problem. I access the database to populate the first view, but when I want to populate the second view, it doesn't enter sqlite3_prepare_v2 of the query... so this must mean the database is still in use by the old query..
So what is the proper way of handling closing a database after use?
Currently I do a query like this:
-(NSArray *)findAllArtists
{
NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
NSString *query = #"SELECT * FROM Painting GROUP BY Artist";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil)
== SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
char *uniqueIdChars = (char *) sqlite3_column_text(statement, 0);
char *artistChars = (char *) sqlite3_column_text(statement, 1);
NSString *uniqueId = [[NSString alloc] initWithUTF8String:uniqueIdChars];
NSString *artist = [[NSString alloc] initWithUTF8String:artistChars];
PaintingInfo *info = [[PaintingInfo alloc] initWithUniqueId:uniqueId artist:artist];
[returnArray addObject:info];
[uniqueId release];
[artist release];
}
sqlite3_finalize(statement);
}
sqlite3_close(database);
return returnArray;
}
You should look at fmdb wrapper at github. Even if you don't use it, look at the code.
Where are you opening the database? You're closing it in this code. Before you call it again, it needs to be open. You should consider just keeping it open for the duration of the single user iOS app and closing when you're done. What happens if you simply remove the close call?
The first thing you should do is check all your return codes for sqlite calls. for example, with step you're not handling anything other than SQLITE_ROW. At least log others. Also for finalize and close you're not handling or logging others.
Also, you're preparing (compiling) the sql statement but your not saving it off. prepare_v2 gives you back a compiled statement. Save it off as a member variable and call reset against it before using it again.
To answer your specific question of how to close - you need to consider that some statements may not have been finalized. Here's my close method: (BTW, ENDebug is my wrapper over NSLog)
- (void)close
{
if (_sqlite3)
{
ENInfo(#"closing");
[self clearStatementCache];
int rc = sqlite3_close(_sqlite3);
ENDebug(#"close rc=%d", rc);
if (rc == SQLITE_BUSY)
{
ENError(#"SQLITE_BUSY: not all statements cleanly finalized");
sqlite3_stmt *stmt;
while ((stmt = sqlite3_next_stmt(_sqlite3, 0x00)) != 0)
{
ENDebug(#"finalizing stmt");
sqlite3_finalize(stmt);
}
rc = sqlite3_close(_sqlite3);
}
if (rc != SQLITE_OK)
{
ENError(#"close not OK. rc=%d", rc);
}
_sqlite3 = NULL;
}
}
finally, consider adding much more logging along with the return codes so you can get more insight.
Hope that helps.
I'm trying to make an app, that reads from an SQLite3 database. I plan to pre-load data during development, so the app does not need to modify anything in the database, only read from it, make queries, etc.
What is the best practice for solely reading data? Should I open the database, read the data, and close it, with each query? The app will be making many small queries and a few large ones. Is it better to have the database open for the duration of the app, or open/close it with each fetch?
Reading:
1. For queries, it's important to re-use compiled statements.
2. Make sure you use parameters so you can re-use those compiled queries
When you call sqlite3_prepare_v2, it compiles the statement and gives you a reference to the statement back. Find a way to save that off and re-use it. See the code below for *statement. You pass &statement into prepare.
Also, note the use of ? for parameters. If you're going to re-use the statement, it's important to call sqlite3_reset() againt the statement, rebind the inputs from the program (parameters) and execute it again.
sqlite3_stmt *statement;
NSString *querySQL = #"update contacts set name=?,address=?,phone=? where id=?";
NSLog(#"query: %#", querySQL);
const char *query_stmt = [querySQL UTF8String];
// preparing a query compiles the query so it can be re-used.
// find a way to save off the *statement so you can re-use it.
sqlite3_prepare_v2(_contactDb, query_stmt, -1, &statement, NULL);
// use sqlite3_bind_xxx functions to bind in order values to the params
sqlite3_bind_text(statement, 1, [[contact name] UTF8String], -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 2, [[contact address] UTF8String], -1, SQLITE_STATIC);
sqlite3_bind_text(statement, 3, [[contact phone] UTF8String], -1, SQLITE_STATIC);
sqlite3_bind_int64(statement, 4, [[contact id] longLongValue]);
Always check the return codes! and log or handle the errors.
rc = sqlite3_step(stmt);
switch (rc)
{
case SQLITE_ROW:
// ...
break;
case SQLITE_OK:
case SQLITE_DONE:
break;
default:
// ....
}
return NO;
}
if you get an error, log or get the eror message to provide more info:
- (NSString*)errorMessage
{
return [NSString stringWithCString:sqlite3_errmsg(_sqlite3) encoding:NSUTF8StringEncoding];
}
Use sqlite_open_v2 with the SQLITE_OPEN_READONLY flag. For example, I use the following method to open a database for reading only.
// Open for reading only.
- (int) openDatabaseAtPath:(NSString *) path
{
if (database != nil)
{
sqlite3_close(self.database);
[self setDatabase:nil];
}
int errorCode = SQLITE_OK;
errorCode = sqlite3_open_v2([path UTF8String],
&database,
SQLITE_OPEN_READONLY,
NULL);
return errorCode;
}
Unless you copy the database to the Documents directory, you work with the DB from the resource dir, and that one is readonly.
When you open the database using sqlite_open_v2 and the SQLITE_OPEN_READONLY flag, SQLite opens the file itself in read-only mode, so even if your application, due to a bug, corrupts memory belonging to SQLite, the database will stay untouched.
With this in mind, I'd keep the database open until the application quits. (You may wish to close it if you receive a low-memory notification and reopen it on demand, but opening and closing it for every query would be wasteful.)
As per you question you want to read data from database. So following are the answer of you questions.
No need to open db each time when you fire the query. Just one it one time. It is better if you create singleton class and open db in it for first time when it initiate.
Use following code method which will work for all select queries which has conditional select, group by etc.
I) it takes Output/result column names as input array ii ) TableName iii) Where condition iv)OrderBy clause v)group By clause
- (NSMutableArray *)runSelecteQueryForColumns: (NSArray *)p_columns ontableName: (NSString *)p_tableName withWhereClause: (NSString *)p_whereClause withOrderByClause: (NSString *)p_orederByCalause withGroupByClause: (NSString *)p_groupByClause
{
NSMutableArray *l_resultArray = [[NSMutableArray alloc] init];
if(!self.m_database)
{
if(![self openDatabase])
{
sqlite3_close(self.m_database);
//NSLog(#"error in select : DB creating : %#",p_whereClause);
return nil;
}
}
NSMutableString *l_simpleQuery =[[NSMutableString alloc] initWithString:#"Select"] ;
if(p_columns)
{
for(int l_row = 0 ; l_row < [p_columns count] ; l_row++)
{
if(l_row != [p_columns count]-1)
{
[l_simpleQuery appendString:[NSString stringWithFormat:#" %#,", [p_columns objectAtIndex:l_row]]];
}
else
{
[l_simpleQuery appendString:[NSString stringWithFormat:#" %#", [p_columns objectAtIndex:l_row]]];
}
}
}
else
{
[l_simpleQuery appendString:#" *"];
}
[l_simpleQuery appendString:[NSString stringWithFormat:#" From %#",p_tableName]];
if(p_whereClause)
{
[l_simpleQuery appendString:[NSString stringWithFormat:#" %#",p_whereClause]];
}
if(p_groupByCaluase)
{
[l_simpleQuery appendString:[NSString stringWithFormat:#" %#",p_groupByCaluase]];
}
if(p_orederByCalause)
{
[l_simpleQuery appendString:[NSString stringWithFormat:#" %#",p_orederByCalause]];
}
//NSLog(#"Select Query: - %#",l_simpleQuery);
const char *l_query_stmt = [l_simpleQuery UTF8String];
sqlite3_stmt *l_statement = nil;
int i = sqlite3_prepare_v2(self.m_database,
l_query_stmt, -1, &l_statement, NULL);
if (i == SQLITE_OK)
{
while(sqlite3_step(l_statement) == SQLITE_ROW)
{
[l_resultArray addObject:[self createDictionary:l_statement]];
}
sqlite3_finalize(l_statement);
}
else
{
sqlite3_finalize(l_statement);
//sqlite3_close(l_database);
DDLogError(#"%# - error in SQL :%#",THIS_FILE,l_simpleQuery);
return nil;
}
//NSLog(#"RESULT %#",l_resultArray);
return l_resultArray;
}