I'm working on an iPhone App which uses sqlite. I am trying to insert a record on a table. My code runs fine but it does not populate the table. My code is as shown below. Can someone help on what is wrong with the method. Thanks for the help:
- (void) saveProductDetails: (int)pklItemID :(NSString*)sItemDescription :(NSString*)barcodeValue :(int)lRemainingItems :(float)lCostPrice :(float)lSellingPrice
{
// The array of products that we will create
// NSMutableArray *products = [[NSMutableArray alloc] init];
NSLog(#"The ItemID in DBMethod is %d",pklItemID);
NSLog(#"The Selling Price in DBMethod is %f",lSellingPrice);
NSLog(#"The Cost Price in DBMethod is %f",lCostPrice);
NSLog(#"The Stock Quantity in DBMethod is %d",lRemainingItems);
NSDate* now = [NSDate date];
NSString *insertSQL = [NSString stringWithFormat: #"INSERT INTO Spaza_Inventory (fklSpazaID,fklItemID,lRemainingItems,lCostPrice,lSellingPrice,fklUserID,fklSalesID,fklOrderListID,dtCostEffective,dtPriceEffective)\
VALUES ('%d','%d',' %d','%.02f','%.02f','%d','%d','%d','%#','%#')",0,pklItemID, lRemainingItems, lCostPrice, lSellingPrice,0,0,0,now, now];
NSLog(#"The SQl String is %#",insertSQL);
const char *sql = [insertSQL UTF8String];
//To run the above SQL in our code, we need to create an SQLite statement object. This object will execute our SQL against the database.
// The SQLite statement object that will hold the result set
sqlite3_stmt *statement;
// Prepare the statement to compile the SQL query into byte-code
int sqlResult = sqlite3_prepare_v2(database, sql, -1, &statement, NULL);
//After preparing the statement with sqlite3_prepare_v2 but before stepping through the results with sqlite3_step, we need to bind the parameters. We need to use the bind function that corresponds with the data type that we are binding.
sqlite3_bind_int(statement, 2, pklItemID);
sqlite3_bind_int(statement, 3, lRemainingItems);
sqlite3_bind_double(statement, 4, lCostPrice);
sqlite3_bind_double(statement, 5, lSellingPrice);
//sqlite3_bind_int(statement, 5, pklItemID);
//If the result is SQLITE_OK, we step through the results one row at a time using the sqlite3_step function:
if ( sqlResult== SQLITE_OK) {
// Step through the results - once for each row.
NSLog(#"Record Updated");
// Finalize the statement to release its resources
sqlite3_finalize(statement);
}
else {
NSLog(#"Problem with the database:");
NSLog(#"%d",sqlResult);
}
//return products;
}
sqlite3_step(statement);
Add above statement after binding.
From what I am seeing, you are preparing the query, but never actually executing it...
To execute the query you need to call sqlite3_step. Do this after binding all of the variables.
You should also check the result of sqlite3_prepare_v2 right away, before calling any of the bind statements.
Related
Is it possible to execute two or more select statement in one query in SQLite? For example,
We can execute create or insert query,
NSString *create_query = #"create table if not exists Employee (id integer primary key, firstName text, lastName text);create table if not exists Department (id integer primary key, department text, devision text)";
By using,
sqlite3_exec(self.contactDB,[create_query UTF8String], NULL, NULL, &errorMessage) == SQLITE_OK)
we can execute it.
But if query is something like,
NSString *select_query = #"select * from Employee;select * from Department";
Then is it possible to execute? If yes then how to differentiate data from sqlite3_step?
Generally we are fetching data like,
if (sqlite3_prepare_v2(self.contactDB, [select_query UTF8String], -1, &statement, NULL) == SQLITE_OK) {
NSLog(#"prepared from data get");
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *department = [[NSString alloc]initWithUTF8String:(const char*)sqlite3_column_text(statement, 1)];
NSString *devision = [[NSString alloc]initWithUTF8String:(const char *)sqlite3_column_text(statement, 2)];
NSLog(#"Department : %#, Devision : %#",department,devision);
}
NSLog(#"errror1 is %s",sqlite3_errmsg(self.contactDB));
}
But if there is a two select statement then how to identify column and row in sqlite3_step?
We can execute two select statements together (i.e. select * from Employee;select * from Department ) in terminal, so it should some way in iOS I think.
Yes, you can use sqlite3_exec() to execute two SELECT statements in one call. You just have to provide a callback function where you handle the result rows. I've never used that feature, and how I understand the doc you're on your own to distinguish the rows of each statement; perhaps one can use the column count for that.
I advise against using sqlite3_exec() that way; it seems rather tedious and error-prone. sqlite3_prepare_*() should be the way to go, and it can only handle one result set (SELECT query), but you can have open multiple statements at a time, represented by the ppStmt handle. If you have problems with that you should describe them instead of posting a XY Problem question.
We can perform this by using C style callback function with sqlite3_exec.
There is no proper code available on internet (I haven't found!) so i would like to answer with code sample.
We can implement C - style callback method like
int myCallback(void *parameter, int numberOfColumn, char **resultArr, char **column)
{
NSLog(#"number of column %d",numberOfColumn); // numberOfColumn is return total number of column for correspond table
NSString *columnName = [[NSString alloc]initWithUTF8String:column[0]]; // This will return column name column[0] is for first, column[1] for second column etc
NSLog(#"column name is %#",columnName);
NSString *result = [[NSString alloc]initWithUTF8String:resultArr[2]]; // resultArr returns value for row with respactive column for correspond table. resultArr[2] considered as third column.
NSLog(#"result is %#",result);
return 0;
}
And we can call this callback function in our sqlite3_exec function like,
NSString *getData = #"select * from Department;select * from Employee";
if (sqlite3_exec(self.contactDB, [getData UTF8String], myCallback, (__bridge void *)(self), &err) == SQLITE_OK ) {
if (err) {
NSLog(#"error : %s",err);
}
else {
NSLog(#"executed sucessfully");
}
}
We have make bride : (__bridge void *)(self) as parameter of sqlite3_exec. We can pass NULL in this case because we have implemented c style function. But if we implement Objective - c style function or method then we must pass (__bridge void *)(self) as parameter.
So, By callback function we can execute multiple queries in one statement whether it is select type queries or else.
Reference : One-Step Query Execution Interface
I am learning how to use sqlite in IOS
I am using the following to insert a record into a table
int DBID = [_db ExecuteINSERT:#"INSERT INTO LOG(NAME , COMPANY , CATEGORY) VALUES('txt1','txt2','txt3')"];
This works fine and adds the three strings into a row
I want to exchange the hardcoded strings for string variable but can work out what to do. I tried
NSString* myString1 = #"String1";
NSString* myString2 = #"String2";
NSString* myString3 = #"String3";
int DBID = _db ExecuteINSERT:#"INSERT INTO LOG(NAME , COMPANY, CATEGORY) VALUES(myString1, myString2, myString3)"];
But I get a sqlite error 1 no such column but there is as it works with hardcoded strings
any ideas where I'm going wrong?
Any help appreciated
Mark
You are declaring the same string 3 times, but I will assume that this is just a typo in your question, and what you really have (or at least meant) is:
NSString* string1 = #"String1";
NSString* string2 = #"String2";
NSString* string3 = #"String3";
Your next problem is that you have included the variable names as literal strings inside the " - This means that the SQL statement will contain "string1" not the value of string1 which is what you are after.
Since you are using a wrapper around SQLite, you can use stringWithFormat to create the insert statement with the values of the string variables substituted
NSString *sqlStatement=[NSString stringWithFormat:#"INSERT INTO LOG(NAME, COMPANY,CATEGORY) values (\"%#\",\"%#\",\"%#\")",string1,string2,string3];
int DBID = [_db ExecuteINSERT:sqlStatement];
It is worth noting that this is unsafe since you cannot guard against SQL injection where the values are obtained from user input. This may not be a concern on a mobile device; the worst a user can do is affect their own database.
The technically correct approach if you weren't using a wrapper is to use sqlite3_prepare_v2 -
sqlite3_stmt *statement;
NSString *insertSQL = #"INSERT INTO LOG(NAME, COMPANY,CATEGORY) values (?,?,?)";
const char *insert_stmt = [insertSQL UTF8String];
if (sqlite3_prepare_v2(_db, insert_stmt,-1, &statement, NULL)!= SQLITE_OK) {
// LOG THE ERROR
NSLog(#"Error: failed to perpare statement with message '%s'.", sqlite3_errmsg(_db));
}
else {
// Bind values to the statement
sqlite3_bind_text(statement,1,[string1 UTF8String],-1,SQLITE_TRANSIENT);
sqlite3_bind_text(statement,2,[string2 UTF8String],-1,SQLITE_TRANSIENT);
sqlite3_bind_text(statement,3,[string3 UTF8String],-1,SQLITE_TRANSIENT);
double retValue = sqlite3_step(statement);
if (retValue == SQLITE_DONE) {
NSLog(#"Insert successful");
}
sqlite3_reset(statement);
sqlite3_finalize(statement);
}
This second block of code creates a statement with three placeholders (?,?,?) and then substitutes strings into those placeholders with sqlite3_bind_text. Finally, the statement is executed with sqlite3_step.
The last couple of lines just clean up.
I have a area table in sqlite database. Everytime i am just performing insert operation onto the sqlite database. How can i check if any record exists or not. If not exist simply insert. If exist then update records.
Please help me.
you can do easily "insert or ignore into tbl_name"
here you can see the example
http://www.raywenderlich.com/913/sqlite-tutorial-for-ios-making-our-app
this would be usefull for you....
http://www.sqlite.org/lang_conflict.html
Yes, you can do that with a single query.
INSERT ON CONFLICT IGNORE should help you: http://www.sqlite.org/lang_conflict.html
Put a unique key on the name, this will create a conflict when you try inserting a record if the name already exists.
The default is ABORT, so without the IGNORE, the statement will return an error. If you don't want that, use IGNORE.
You can do INSERT OR REPLACE if you have a primary key on the table. For example:
sqlite3 *database = NULL;
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:#"test.sqlite"];
int rc = sqlite3_open([path UTF8String], &database);
NSAssert(rc == SQLITE_OK, #"Open failed");
// note, use PRIMARY KEY when creating table
rc = sqlite3_exec(database, "CREATE TABLE IF NOT EXISTS test (animal TEXT PRIMARY KEY, sound TEXT)", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"Create failed: %s", sqlite3_errmsg(database));
// create a record that will be replaced by the subsequent `INSERT OR REPLACE`
rc = sqlite3_exec(database, "INSERT INTO test (animal, sound) VALUES ('dog', 'meow')", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"INSERT failed: %s", sqlite3_errmsg(database));
// this will REPLACE entry if value with same PK found, otherwise it would INSERT
rc = sqlite3_exec(database, "INSERT OR REPLACE INTO test (animal, sound) VALUES ('dog', 'woof')", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"INSERT failed: %s", sqlite3_errmsg(database));
// now retrieve values and make sure it worked like we thought it would
sqlite3_stmt *statement = NULL;
rc = sqlite3_prepare_v2(database, "SELECT animal, sound FROM test", -1, &statement, NULL);
NSAssert(rc == SQLITE_OK, #"prepare SELECT failed: %s", sqlite3_errmsg(database));
while ((rc = sqlite3_step(statement)) == SQLITE_ROW) {
const unsigned char *animal = sqlite3_column_text(statement, 0);
const unsigned char *sound = sqlite3_column_text(statement, 1);
NSLog(#"%s goes %s", animal, sound);
}
NSAssert(rc == SQLITE_DONE, #"step failed: %s", sqlite3_errmsg(database));
sqlite3_finalize(statement);
sqlite3_close(database);
And that will report that the INSERT OR REPLACE replaced the previous value rather than inserting second record:
2013-11-21 08:59:25.285 AnimalSounds[53549:70b] dog goes woof
If you don't have primary key, rather than this simple INSERT OR REPLACE, you'd have to break it into two steps, either:
Look for record with SELECT: If found, do UPDATE; if not found, do INSERT.
First DELETE any records that would match whatever criteria you want, and then do INSERT.
This first approach is a bit safer, but you could use the second approach if you had to (though you would probably use transactions a do a ROLLBACK if you had any problems). Needless to say, the INSERT OR REPLACE approach is even easier, but requires a primary key.
First call get record query in Database. Here I am add a example, I am checking that user login information available in database or not. So add below code. IF User record is available than i get record array otherwise nil.
+(NSArray*)getTBL_LOGIN
{
NSMutableArray *Favourite=[[NSMutableArray alloc]init];
sqlite3 *database;
TabBarAppDelegate *x=(TabBarAppDelegate*)[[UIApplication sharedApplication]delegate];
if(sqlite3_open([[x dataBasePath] UTF8String],&database) == SQLITE_OK) {
NSString *str = [NSString stringWithFormat:#"select * from tbl_login"];
const char *sqlStmt=[str UTF8String];
sqlite3_stmt *compiledStmt;
if(sqlite3_prepare_v2(database, sqlStmt, -1, &compiledStmt, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStmt)==SQLITE_ROW)
{
NSString *uid=[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStmt, 0)];
NSString *username=[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStmt, 1)];
NSDictionary *d=[NSDictionary dictionaryWithObjectsAndKeys:uid,#"uid",username,#"username",nil];
[Favourite addObject:d];
}
}
sqlite3_finalize(compiledStmt);
}
sqlite3_close(database);
if([Favourite count]>0)
{
NSArray *ar=[NSArray arrayWithArray:Favourite];
return ar;
} else {
return nil;
}
}
If you get the record count >=1 then record exist so you have to call update query if you get record count 0 than record is not available in database so you have to call insert query
In a situation where I imported all updates into another database table, I could use following:
-- Existing table: t(uc UNIQUE, v1, v2, v3);
-- Updates table: ut(uc UNIQUE, v2);
INSERT OR REPLACE INTO t
SELECT ut.uc, et.v1, ut.v2, et.v3 FROM ut
LEFT JOIN t AS et ON ut.uc=et.uc;
This statement will insert new rows from ut into t. Existing rows are replaced with a row containing new data from ut and existing data from t.
For this to work, you must have a UNIQUE column (which makes sense as you are looking for a row update or insert a new one), and have new data available so it can be queried (in same or another database).
This worked for me, hope it may help you.
Another solution, maybe with better performance is using two statements:
UPDATE t SET v1='some value', v2=123 WHERE unique_col='some_id';
INSERT OR IGNORE t(v1, v2, unique_col) VALUES('some value', 123, 'some_id');
UPDATE will become a null operation when 'some_id' is not found.
INSERT will ignore all existent 'some_id'.
i have this odd bug where i'm querying my table 'Children' with quite a complex query. It works fine, but for some reason it bugs a this other view from updating the database. You see this database holds stickers and one easy way to give them is to access this admin page, which is where its bugging. I can query the information fine, BUT ! when i update the table it hates and doesn't work. But its strange inside the core view controller it doesn't bug when i update the table there. I narrowed down the code to the cause of this problem:
-(void)leaderboardsystem
{
NSString *nexttargetsql = [NSString stringWithFormat:#"SELECT * FROM Children WHERE Completed > %d OR (Completed = %d AND Current_Stickers > %d) ORDER BY Completed ASC, Current_Stickers ASC LIMIT 1",completecount,completecount,stickercount]; //Queries table for the childs name and returns more data.
NSString *behindyousql = [NSString stringWithFormat:#"SELECT * FROM Children WHERE Completed < %d OR (Completed = %d AND Current_Stickers < %d) ORDER BY Completed DESC, Current_Stickers DESC LIMIT 1",completecount,completecount,stickercount];
nexttarget.text = [self leaderboardQuery:nexttargetsql];
behindyou.text = [self leaderboardQuery:behindyousql];
}
-(NSString*)leaderboardQuery:(NSString*)sql//does the querying
{
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(Childdb, [sql UTF8String], -1, &statement, nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
char *ffield1 = (char *) sqlite3_column_text(statement, 0);
NSString *ffield1Str = [[NSString alloc]initWithUTF8String:ffield1];
char *ffield2 = (char *) sqlite3_column_text(statement, 8);
NSString *ffield2Str = [[NSString alloc]initWithUTF8String:ffield2];
char *ffield3 = (char *) sqlite3_column_text(statement, 10);
NSString *ffield3Str = [[NSString alloc]initWithUTF8String:ffield3];
NSLog(#"Name:%#",ffield1Str);
NSLog(#"this is completecount: %#", ffield2Str);
NSLog(#"this is stickcount: %#",ffield3Str);
return ffield1Str;
}
}
return NULL;
}
whenever i call the method leaderboardsystem it causes this bug, but if i don't then it works fine ! funny enough, i was a little surprised to be honest. It surprises me because it affects a completely different view controller that has no connection to the main view. The table layout is:
[self createTable:#"Children" withField1:#"Name" withField2:#"Password" withField3:#"House" withField4:#"Sticker Collection" withField5:#"Tickets Gathered" withField6:#"Tickets Removed" withField7:#"Last Ticket Scanned" withField8:#"Current Tickets" withField9:#"Completed" withField10:#"Complete" withField11:#"Current_Stickers"];
This is the updating code that seems to fail when i include the leaderboard system in the main view(this is on a completely different view controller)
-(void)UpdateDatabase//update table, if value has been incremented
{
NSString *sql = [NSString stringWithFormat:#"UPDATE Children SET 'Current Tickets' = %d, 'Tickets Removed' = %d, 'Tickets Gathered' = %d WHERE Name = '%#'",[self.currenttickets.text integerValue], [self.removedtickets.text integerValue], [self.totaltickets.text integerValue], name];
[self updatetable:sql];
}
-(void)updatetable:(NSString*)sql
{
char *err;
if (sqlite3_exec(Childdb, [sql UTF8String], NULL, NULL, &err)!=SQLITE_OK) {
sqlite3_close(Childdb);
NSAssert(0, #"Could not update Table");
} else {
NSLog(#"Table updated");
}
}
I'm trying not to overload the description here and keeping it brief my program is quite large, if you require any more information let me know. BUT i guarantee that the leaderboard system is causing the problem. Thanks a million if you can solve this problem, been working on it all day ! :(
ALSO it also disturbs the place where i add records to the table, so the updating code is not causing it. Its that leaderboard query, no idea why :(
Okay, well i didn't want to do this. But instead of having a method to manage the query, i just repeated it twice, inside the method, but it worked ! Not sure what was wrong with it. Seems no one else could help me so, i'll just leave it to that.
You need to call sqlite3_finalize(Childdb). It would look something like the following:
-(NSString*)leaderboardQuery:(NSString*)sql//does the querying
{
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(Childdb, [sql UTF8String], -1, &statement, nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
...
}
}
sqlite3_finalize(Childdb);
return NULL;
}
This is a really simple example because there is no error handling. The documentation that describes this is the following (located here):
*ppStmt is left pointing to a compiled prepared statement that can be executed using sqlite3_step(). If there is an error, *ppStmt is set to
NULL. If the input text contains no SQL (if the input is an empty
string or a comment) then *ppStmt is set to NULL. The calling
procedure is responsible for deleting the compiled SQL statement using
sqlite3_finalize() after it has finished with it. ppStmt may not be
NULL.
This example code may help you: https://github.com/ccgus/fmdb/blob/master/src/FMDatabase.m#L519
So, when i execute the query directly in sqlite it returns me the values in the order as I am expecting it to be. But when I display it on my iOS app, it sorts the values in ascending order which I don't want to happen. Can anybody help me to get over this problem?!
Thanks in advance!! :-)
Has your query an "order by" statement? Maybe this way gives you the control to get the results as you want.
What you are trying to achieve shouldn't be a problem:
make your SQL query:
it will probably looks like this select columname from yourtable order by asc asc will sort the result in alphabetic order if you want your result to be ordered
read the result of the SQL query line by line. Here is some code snippet to do so:
NSString *SQLQuery = #"your SQL query here"
// The SQL statement that you plan on executing against the database
const char *sql = [SQLQuery UTF8String];
// The SQLite statement object that will hold your result set
sqlite3_stmt *statement;
// Prepare the statement to compile the SQL query into byte-code
int sqlResult = sqlite3_prepare_v2(database, sql, -1, &statement, NULL);
if (sqlResult== SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
// feed the result in a mutable array here
// e.g [yourmutablearray addObj: yourObj];
}
// Finalize the statement to release its resources
sqlite3_finalize(statement);
}
else // catch up any possible issue here
{
NSLog(#"Problem excuting SQL statement on the Database:");
NSLog(#"%d",sqlResult);
}