In my application I need to get the data from the last inserted into table. What I have done is this
NSString *query = [NSString stringWithFormat:#"SELECT * FROM PHONEBOOK ORDER BY NAME DESC LIMIT 1;"];
// NSLog(#"query : %#",query);
BOOL recordExist = [self recordExistOrNot:query];
if (!recordExist) {
// save data if no record exists
}
-(BOOL)recordExistOrNot:(NSString *)query{
BOOL recordExist=NO;
sqlite3_stmt *statement;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &myDatabase) == SQLITE_OK) {
if (sqlite3_prepare_v2(myDatabase, [query UTF8String], -1, &statement, nil)==SQLITE_OK)
{
if (sqlite3_step(statement)==SQLITE_ROW)
{
recordExist=YES;
NSString *Name = [[NSString alloc]
initWithUTF8String:
(const char *) sqlite3_column_text(
statement, 0)];
NSString *PhoneNumber = [[NSString alloc]
initWithUTF8String:
(const char *) sqlite3_column_text(
statement, 1)];
//here i need to get the Name and number from the last inserted data
NSLog(#"version is %#, type is %#",Name ,PhoneNumber);
}
else
{
NSLog(#"%s,",sqlite3_errmsg(myDatabase));
}
sqlite3_finalize(statement);
sqlite3_close(myDatabase);
}
}
return recordExist;
}
The problem is I'm not getting the last inserted data. I am getting the data from other rows ( i am not sure whether it is from top row or any other) .
Could you please tell me what am I doing wrong here.
Thanks in advance.
If doing this immediately after inserting the row, you can use the SQLite C function sqlite3_last_insert_rowid() to get the rowid for the last inserted row. You can also use the last_insert_rowid() SQL function:
SELECT * FROM PHONEBOOK WHERE ROWID = last_insert_rowid()
Note, you probably have to keep the SQLite database open to do this (but you shouldn't be opening and closing the database for every SQL statement, anyway).
If you're doing this at some later date (e.g. the user terminates the app, comes back to it tomorrow, and wants to see the last inserted record), then you have to rely upon your own database model to handle that. For example, I frequently will have create/modify/delete timestamps in my table that I update every time I INSERT or UPDATE the row, and you can then sort based upon those values.
Related
Table values:
ID=1 CUSTOMERID=1 NAME=John EMAIL=email USERNAME=usernaeme
I am using this code to fetch customerId from Usertable with this code
#try {
//CustomerIdField=#"admin";
// customerUsername=#"admin";
NSLog(#"the value of customerusername is %#",customerUsername);
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
NSLog(#"inside function");
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"EBook.db" ]];
const char *dbpath;
#try {
dbpath = [databasePath UTF8String];
NSLog(#"the const char is %s",dbpath);
}
#catch (NSException *exception) {
NSLog(#"the exception3 is %#",exception);
}
if (sqlite3_open(dbpath, &Ebookreaderdb) == SQLITE_OK)
{
NSString *selectSQL = [NSString stringWithFormat: #"SELECT CUSTOMERID FROM Usertable WHERE EMAIL=\"%#\"",customerUsername];
sqlite3_stmt *selstatement;
const char *select_stmt = [selectSQL UTF8String];
//NSMutableArray *resultArray = [[NSMutableArray alloc]init];
if (sqlite3_prepare_v2(Ebookreaderdb,
select_stmt, -1, &selstatement, NULL ) == SQLITE_OK)
{
NSLog(#"inside sqlite OK"); //this prints in log
if (sqlite3_step(selstatement) == SQLITE_ROW)
{
NSLog(#"inside sqlite ROW"); // this is also printing in log
NSString *userInfoStr = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selstatement,1)];
NSLog(#"val is %#",userInfoStr);
char *tmp = sqlite3_column_text(selstatement,1);
if (tmp == NULL)
CustomerIdField = nil;
else
CustomerIdField = [[NSString alloc] initWithUTF8String:tmp];
CustomerIdField = [[NSString alloc] initWithUTF8String:
(const char *) sqlite3_column_text(selstatement,1)];
NSLog(#"inside customer is %#",CustomerIdField);
// [resultArray addObject:name];
}
else{
NSLog(#"Not found");
// return nil;
}
sqlite3_reset(selstatement);
}
But i am getting this exception Newpjtonfriday[2165:84017] the exception is 2 *** +[NSString stringWithUTF8String:]: NULL cString
I googled with the above result and everywhere it is saying that the value is null that is why the exception occurs. but in my case the value is there.
Because the code
NSLog(#"inside sqlite ROW");
is coming in log meaning that a row exists in table. But cannot fetch it.
Please help
I think it's a typo:
#"SELECT CUSTOMERID FROM Usertable WHERE EMAIL=\"%#\"",customerUsername;
You are passing the username instead of the e-mail? Or maybe you meant to SELECT ... WHERE USERNAME=?
Also there is no need for any of those #try/#catch blocks as I cannot see how an Objective-C exception can be thrown by that code.
One last thing; in order to avoid SQL Injection attacks you should bind the values into your statements, rather than formatting them as a string, as you have done here.
Little advise if you are just starting to develop on iOS, try to use some library(FMDB to easy work with SQLite) to make some task more straight forward.
Answer to your Question
Try the "SELECT * FROM Usertable" if it has any row in your table.
Second you should check if the value in your row is not NULL.
To get easy solution use something like Datum SQLite Free(Mac os X) OR you could use SELECT COUNT to ensure you have any rows in table.
Try your select and if the app return the rows but there is no value in your column that's mean you have problems with write to DB logic but not in read logic and you are trying to fetch row with have null value on column you want.That's why this method is not working:
[NSString stringWithUTF8String:]: NULL cString
its thrown an exception because you have no string and trying to send NULL to method.
The sqlite3_column_text index number is zero-based, not one-based. Thus, this line:
char *tmp = sqlite3_column_text(selstatement, 1);
Should be:
char *tmp = sqlite3_column_text(selstatement, 0);
By the way, your handling of this tmp variable is a prudent way of checking to make sure it's not NULL before you use it. Unfortunately, elsewhere in this same routine you use sqlite3_column_text value directly (which is why your app crashed, rather than gracefully reporting the error). You have several redundant calls to sqlite3_column_text here. I would suggest employing the pattern you used with this tmp variable.
Let me give you my suggestion I tried what you just did and I also got the error. Then I did some googling and found this link
http://www.raywenderlich.com/913/sqlite-tutorial-for-ios-making-our-app
just modify you codes from these
char *tmp = sqlite3_column_text(selstatement,1);
if (tmp == NULL)
CustomerIdField = nil;
else
CustomerIdField = [[NSString alloc] initWithUTF8String:tmp];
CustomerIdField = [[NSString alloc] initWithUTF8String:
(const char *) sqlite3_column_text(selstatement,1)];
NSLog(#"inside customer is %#",CustomerIdField);
to
int tmp = sqlite3_column_int(selstatement,0);// my case it is int
char *nameChars = (char *) sqlite3_column_text(selstatement, 1);// here is the change occuring please refer this
NSString *name = [[NSString alloc] initWithUTF8String:nameChars]; // there are two steps first fetch as char, then change to String
NSLog(#"customerid is %d",tmp);
NSLog(#"customer name is %#",name);
Try this, It worked for me..
I think the problem is that you are fetching the coustomerId as String directly
In you case just try this
char *custId = (char *) sqlite3_column_text(selstatement, 0);
NSString *customerId = [[NSString alloc] initWithUTF8String:custId];
:-)
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?
I to use sqlite database in my application. in my sqlite exist 10 records. I want read 4 data from this database and I want get this data until BroID in last data to be NULL (BroID is one of columns data) this is my code but I dont know how to use of loop in my code until BroID to be NULL.
-(NSMutableArray *)readInformationFromDatabase
{
array = [[NSMutableArray alloc] init];
// Setup the database object
sqlite3 *database;
// Open the database from the users filessytem
if(sqlite3_open([[self dataFilePath] UTF8String], &database) == SQLITE_OK)
{
// I want to use loop for certain work!!! (this work is get name from data base until BroID to be NULL )
NSString *sqlStatement_userInfo =[NSString stringWithFormat:#"Select * from table1"];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, [sqlStatement_userInfo UTF8String], -1, &compiledStatement, NULL) == SQLITE_OK)
{
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Init the Data Dictionary
NSMutableDictionary *_dataDictionary=[[NSMutableDictionary alloc] init];
NSString *_userName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
[_dataDictionary setObject:[NSString stringWithFormat:#"%#",_userName] forKey:#"Name"];
[array addObject:_dataDictionary];
}
}
else
{
NSLog(#"No Data Found");
}
please get me idea for done this code.
OK first the SELECT statement:
Change the statement from SELECT * ... to SELECT colname1, colname2, ... so you are then certain of the order in which columns are returned, and you won't have to refer to the schema in order to find out what order they come back in. This actually saves time.
BroID must be included in the columns being selected.
You'll want an ORDER BY clause in order to get consistent results.
You can probably get the database to only include rows WHERE BroID IS NOT NULL, which might suite your needs.
If you still need to use code to stop the fetching, then simply test for a NULL BroID column and break out of the while loop using:
while (sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Fetch other columns
if (sqlite_column_type(compiledStatement, INDEX) == SQLITE_NULL)
break;
}
Where INDEX is the column index of BroID.
It's not clear if you want the row where BroID IS NULL in the result set; if you don't then perform the sqlite_column_type() test before fetching the columns, else leave it as above.
Refer to the reference for details.
i have a view Controller called as VegQuantity which does totalcost=(quantity*cost of the dish) and inserts the itemname,quantity,totalcost into a table called as FINALORDER with database name FinalOrder
sqlite3_stmt *statement;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &FinalOrder) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat: #"INSERT INTO FINALORDER (itemname, quantity, totalcost) VALUES (\"%#\", \"%#\", \"%#\")", itemName.text, input.text, output.text];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(FinalOrder, insert_stmt, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
// status.text = #"Contact added";
// name.text = #"";
// address.text = #"";
// phone.text = #"";
NSLog(#"added");
} else {
NSLog(#"Couldnt add");
}
sqlite3_finalize(statement);
sqlite3_close(FinalOrder);
}
Final View Controller viewdidload method
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &FinalOrder) == SQLITE_OK)
{
NSString *querySQL = [NSString stringWithFormat: #"SELECT * FROM FINALORDER"];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(FinalOrder, query_stmt, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *itemname = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
item.text = itemname;
NSString *qua = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
quantity.text = qua;
NSString *total = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 2)];
totalcost.text=total;
}
sqlite3_finalize(statement);
}
sqlite3_close(FinalOrder);
}
But i keep getting this error called expected expression before FinalOrder,and is it correct for me to write this code inside viewdidload? i dont have any button in Final view controller i have a button called order in a view controller called as Restaurant which actually shows me Final view controller..am i supposed to search for the db file again in the Final viewcontroller and i am sorry question seems kind of vague but in brief i just want to know how to retrieve and display the data which i have inserted in VegQuantity view controller into the final view controller thanks
I believe that the problem must rest in the definition of FinalOrder which, on the basis of the error message, looks like has been defined as a class, not as a sqlite3 * variable. Given that the scope of your usage of the database is limited to these two methods, I'd suggest defining a sqlite3 * within that scope, and use that, such as:
- (void)saveRecord
{
sqlite3 *database;
sqlite3_stmt *statement;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
[self purgeTable:database];
NSString *insertSQL = #"INSERT INTO FINALORDER (itemname, quantity, totalcost) VALUES (?, ?, ?)";
if (sqlite3_prepare_v2(database, [insertSQL UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
sqlite3_bind_text(statement, 1, [itemName.text UTF8String], -1, NULL);
sqlite3_bind_int(statement, 2, [input.text intValue]);
sqlite3_bind_double(statement, 3, [output.text doubleValue]);
if (sqlite3_step(statement) == SQLITE_DONE)
{
// status.text = #"Contact added";
// name.text = #"";
// address.text = #"";
// phone.text = #"";
NSLog(#"added");
} else {
NSLog(#"%s Couldn't add; errmsg='%s'", __FUNCTION__, sqlite3_errmsg(database));
}
sqlite3_finalize(statement);
} else {
NSLog(#"%s Couldn't prepare; errmsg='%s'", __FUNCTION__, sqlite3_errmsg(database));
}
sqlite3_close(database);
}
}
Note, in addition to using a sqlite3 * variable for the database, to make this a little more robust:
I have replaced the stringWithFormat statement that built the SQL insert statement with an INSERT statement that uses the ? placeholders and then use sqlite3_bind_text to bind values to that statement. This way, if someone entered a value that included a quotation mark, the insert statement will still work (your original implementation would have crashed and/or was susceptible to a SQL injection attack).
I have also added the sqlite3_errmsg statements so if something goes wrong, I know what the problem was.
Rather than treating these three fields as text fields, I'm assuming your table is defined as CREATE TABLE IF NOT EXISTS FINALORDER (itemname TEXT, quantity INT, totalcost REAL); and therefore use text, int, and double bind statements.
This, incidentally, invokes a purgeTable method, so if you run it twice, it will remove the old record in there:
- (void)purgeTable:(sqlite3 *)database
{
if (sqlite3_exec(database, "DELETE FROM FINALORDER;", NULL, NULL, NULL) != SQLITE_OK)
NSLog(#"%s Couldn't purge table %s", __FUNCTION__, sqlite3_errmsg(database));
}
Anyway, you can then read this data via:
- (void)loadRecord
{
sqlite3 *database;
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
NSString *querySQL = #"SELECT * FROM FINALORDER";
if (sqlite3_prepare_v2(database, [querySQL UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *itemname = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
item.text = itemname;
//[itemname release]; // if not ARC, uncomment this line
int qua = sqlite3_column_int(statement, 1);
quantity.text = [NSString stringWithFormat:#"%1d", qua];
double total = sqlite3_column_double(statement, 2);
totalcost.text = [NSString stringWithFormat:#"%1.2f", total];
}
sqlite3_finalize(statement);
} else {
NSLog(#"%s Couldn't prepare; errmsg='%s'", __FUNCTION__, sqlite3_errmsg(database));
}
sqlite3_close(database);
}
}
Note,
I've added a sqlite3_errmsg log statement if the sqlite3_prepare fails, and I've retired the stringWithFormat (because you weren't formatting anything).
Given that quantity was INT and total was REAL, I'm retrieving the values using the appropriate variation of sqlite3_column_text, sqlite3_column_int, or sqlite3_column_double, as appropriate.
I infer that you're not using ARC and therefore, the [NSString alloc] must have an associated release or autorelease.
Finally, in our chat, you said that you received an error message (presumably an "Undefined Symbols" message) that said:
OBJC_CLASS_$"_FinalOrder"
This means that you are trying to use an object class of FinalOrder, but you haven't defined that class anywhere. Take a look at which .o files it's reporting this error for and look at the corresponding .m file, and look for your use of the FinalOrder class there. You clearly have a FinalOrder class interface defined somewhere, but never defined the class implementation, or, if you have one, for some reason it's not included in your target's "Compile Sources" listing, so double check it's here:
Finally, by the way, make sure your database is in the Documents folder, not trying to open a copy of the database in your project's bundle (because that's read only). If you have a copy of the database in your bundle, just check to see if you already have it in you Documents folder, and if not, copy it from the bundle to the Documents folder.
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.