I am new to objective-C and iphone apps.
I am accessing SQLite and have 3 rows in my table "coffee". I used the following way to grab sth out from the table, however, only then 2nd and 3rd rows are being pulled out {the 1st row is always missed}. Is that due to the logic in my while loop by checking while sqlite3_step(selectstmt) returns SQLITE_ROW is wrong? Here is the code:
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
const char *sql = "select coffeeID, coffeeName from coffee";
sqlite3_stmt *selectstmt;
NSLog(#"sqlite_prepare_v2 returns: %i", sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL));
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
NSLog(#"sqlite3_step returns: %i", sqlite3_step(selectstmt));
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
Coffee *coffeeObj = [[Coffee alloc] initWithPrimaryKey:primaryKey];
coffeeObj.coffeeName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)];
NSLog(#"this is the coffee name: %#", coffeeObj.coffeeName);
coffeeObj.isDirty = NO;
[appDelegate.coffeeArray addObject:coffeeObj];
[coffeeObj release];
}
}
}
On the other hand, is there any convenient way for me to check the number of rows returen in a query directly from the C interface of SQLite?
Many thanks.
You could use the query SELECT COUNT(*) FROM coffee to tell you how many rows there are.
And also, save yourself some headaches and use a SQLite wrapper.
Are the 2 sqlite3_step() calls meant to be executed here?
NSLog(#"sqlite3_step returns: %i", sqlite3_step(selectstmt));
while(sqlite3_step(selectstmt) == SQLITE_ROW {
BTW: there a parenthesis missing in the while line. Do not rewrite your code for SO. Copy/Paste it to avoid copying errors (pasting errors are much more rare)
Related
I have an app I'm developing that will store soccer match statistics but I'm having trouble with my sqlite database. My save method is this :
-(void) saveData{
NSString *databasePath;
sqlite3_stmt *statement;
const char *dbpath =[databasePath UTF8String];
char *errMsg;
NSString *games;
games= #"games";
NSString *entree =[NSString stringWithFormat:#"create table if not exists %#(id integer, home text, away text)", games];
if (sqlite3_open(dbpath, &_gameFile)==SQLITE_OK){
if(sqlite3_exec(_gameFile, [entree UTF8String], NULL, NULL, &errMsg) !=SQLITE_OK){
NSLog(#"executing %s", sqlite3_errmsg(_gameFile));
} else {
NSLog(#"table created (%#)", entree);
}
}
sqlite3_close(_gameFile);
if (sqlite3_open(dbpath, &_gameFile)==SQLITE_OK){
NSString *insertSQL = [NSString stringWithFormat:#"INSERT INTO games (id, home, away) VALUES (\"%#\",\"%#\",\"%#\")", dataPoints[0], dataPoints[1], dataPoints[2]];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(_gameFile, insert_stmt, -1, &statement, NULL);
int rc =sqlite3_step(statement);
if(sqlite3_step(statement) == SQLITE_DONE){
NSLog(#"success");
NSLog(#"tried to enter %#" , insertSQL);
} else if(sqlite3_step(statement) == SQLITE_MISUSE){
NSLog(#"%#", insertSQL);
NSLog(#"Error %s while preparing statement", sqlite3_errmsg(_gameFile));
}
sqlite3_finalize(statement);
sqlite3_close(_gameFile);
}
}
This produces the following entries in the log:
2014-12-10 15:25:32.752 Footy Predictor[1718:513765] table created (create table if not exists games(id integer, home text, away text))
2014-12-10 15:25:32.759 Footy Predictor[1718:513765] INSERT INTO games (id, home, away) VALUES ("1446","Den Haag","Excelsior")
2014-12-10 15:25:32.760 Footy Predictor[1718:513765] Error no such table: games while preparing statement
I'm not really sure where I'm going wrong as the log says table created, and then says no such table.
Any help would be greatly appreciated.
I don't have an explicit answer for you because there are quite a few things that can go wrong with sqlite.
The main thing I can see is that you are not initialising databasePath. This needs to be a path to the .db file e.g. ~/Desktop/myDatabase.db.
1) I assume that you only want to process the statement once and you are actually inserting it 3 times. you need to check the value of rc i.e. only call sqlite3_step once and then put rc into the if statements :
int rc =sqlite3_step(statement);
if(rc == SQLITE_DONE){
NSLog(#"success");
NSLog(#"tried to enter %#" , insertSQL);
} else {
NSLog(#"%#", insertSQL);
NSLog(#"Error %s while preparing statement", sqlite3_errmsg(_gameFile));
}
2) Check the return value of sqlite3_prepare_v2 for success.
3) You neeeeed to normalise your statements (documentation for sqlite3_bind), otherwise you will run into problems when your users enter wildcard characters e.g. "?" or statements like "DROP"
4) Have you considered using Core Data. It is sooo much easier (although not ideal for everyone's needs I know).
So basically I have an app that will provide tasks based on selected project. Both projects and tasks are stored in a SQLite database.
To get the current project id I compare the selected project (_selectedProject) to my database, to get the ID. This is done in my getSelectedProjectId method. However, when running this method in the getTasks method, the Where-statement wont work at all. If I don't run the getSelectedProjectId method first, it works just fine. Am I forgetting to release something? Or is it something else? Any ideas?
I'm pretty new to both SQLite and Objective C, so this may not be a complex issue. I have made sure the getSelectedProjectId method returns the correct project ID. I have also made sure the query that is run in the getTasks method is correct, and when running it through my terminal it returns a number of rows. In the app it returns nothing, provided I'm running the getSelectedProjectId somewhere in that method first.
This is the method that fetches the tasks:
- (void)getTasks
{
[self openDB];
sqlite3_stmt *statement;
int projectId = [self getSelectedProjectId];
NSString *query = [NSString stringWithFormat:#"SELECT * FROM tasks WHERE project_id=%i", projectId];
const char *query_statement = [query UTF8String];
sqlite3_prepare_v2(_contactDB, query_statement, -1, &statement, NULL);
while (sqlite3_step(statement) == SQLITE_ROW)
{
// I add the task title to my array of tasks here.
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
}
And this is the method that gets the correct project id from the database:
- (int)getSelectedProjectId
{
sqlite3_stmt *statement;
NSString *query = [[NSString alloc]
initWithFormat:#"SELECT id FROM projects WHERE title=\"%#\" LIMIT 0,1",
_selectedProject];
int rowId = 0;
const char *query_statement = [query UTF8String];
[self openDB];
sqlite3_prepare_v2(_contactDB, query_statement, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_ROW)
{
rowId = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
return rowId;
}
The problem occured because I closed the DB connection in my getSelectedProjectId-method. I'm now leaving my DB open instead, works like a charm.
I know that the simulator and the actual iOS hardware are not EXACTLY the same, but I'm starting to pull my hair out over this one. I have this code:
sqlite3 *database;
sqlite3_stmt *statement;
int themeCount;
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSString *updateSQL = [NSString stringWithFormat: #"SELECT COUNT(*) FROM Theme"];
const char *update_stmt = [updateSQL UTF8String];
if(sqlite3_prepare_v2(database, update_stmt, -1, &statement, NULL) == SQLITE_OK){
if(sqlite3_step(statement)==SQLITE_ROW)
{
themeCount = sqlite3_column_int(statement, 0);
}
}
sqlite3_finalize(statement);
sqlite3_close(database);
}
With the simulator, it works perfectly fine. Once I push it to my devices, it fails. I've broken it down and came up with the return code where it fails:
if(sqlite3_prepare_v2(database, update_stmt, -1, &statement, NULL) == SQLITE_OK)
If I change that line to capture the code (ie. int x = sqlite3_prepare_v2(...)) it returns 0 with the simulator, 1 with the device. What am I doing wrong here?!?!
Also, for the record, the CREATE statement for the Theme table is:
#"CREATE TABLE Theme (ThemeId INTEGER PRIMARY KEY, ThemeName TEXT, Available BIT);"
(My first thought is that it was case sensitive)
You are not opening the database you think you are opening. The sqlite3_prepare_v2 is the first statement that needs the schema to be present. I suspect your databasePath is incorrect.
You can be more specific with sqlite3_open_v2 by omitting the SQLITE_OPEN_CREATE flag which is the default with sqlite3_open so you don't notice that a new database is being created by the open call. See SQLite3 docs. With the result of
sqlite3_open_v2([databasePath UTF8String], &database, SQLITE_OPEN_READWRITE, NULL)
you will see that the database does not exist.
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);
I have a performance problem when I build the application in the Device. It is actually in my database. I have a table of wine details in which there are 2114 wine names. To get the all those wine names, I wrote this code in the appDelegate:
-(NSMutableArray*)getWineDetails
{
[wineDetailsList removeAllObjects];
sqlite3_stmt* statement;
const char *sql = "select *from wineDetails order by name";
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK)
{
NSAssert1(0, #"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
}
while (sqlite3_step(statement) == SQLITE_ROW)
{
primaryKey = sqlite3_column_int(statement, 0);
//printf("\n primaryKey1 Value:%d",primaryKey);
wineDetails *wineDets = [[wineDetails alloc] initWithPrimaryKey:primaryKey database:database];
[wineDetailsList addObject:wineDets];
//printf("\n ==========================%d",[wineDetailsList count]);
[wineDets release];
}
sqlite3_finalize(statement);
printf("\n Inside AppDelegate .....wineDetailsList count:%d",[wineDetailsList count]);
return wineDetailsList;
}
I am calling this method in the viewWillAppear of another controller where I have to display the wine names in the table view.
The viewWillAppear code:
-(void)viewWillAppear:(BOOL)animated
{
CorkItAppDelegate* appDelegate = (CorkItAppDelegate*)[[UIApplication sharedApplication] delegate];
winesList = [appDelegate getWineDetails];
[tableView reloadData];
}
Here the problem is that when I build it in the device, it takes too much time to navigate into the controller due to the amount of date in the database. What should I do to get rid of this performance issue?
Thanks,
Monish Kumar.
Just as a quick suggestion, you could add an index on the name column, that might speed up the fetch. Also, make sure you're not fetching any more things than you need.