I use Sqlite3 in my iOS app.
If I try to run the command from the console:
INSERT INTO documents (name,fk_1) VALUES ('pluto','')
everything is fine.
But when doing it via prepared statement, the same fails:
INSERT INTO documents (name,fk_1) VALUES (?,?)
saying "foreign key constraint failed".The value of fk_1 = ''.
The table documents is
CREATE TABLE "documents" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL ,"name" varchar,"attivita_id" integer NULL REFERENCES attivita(id) ON DELETE CASCADE)
I was supposing that I can use NULL value as FK, and I can, indeed, because from the command line (first query) it works. The same is not possible with prepared statements?
The code to execute a write is:
-(NSInteger)writeData:(NSDictionary* )data table:(NSString* )table
{
sqlite3_stmt *sqlStatement;
NSMutableArray *columns = [[NSMutableArray alloc] init];
NSMutableArray *values = [[NSMutableArray alloc] init];
NSMutableDictionary *temp = [[NSMutableDictionary alloc] initWithDictionary:data];
#try {
assert([data count] != 0);
if ([[data allKeys] count] == 0) return 1;
// Names/Values for INSERT
// id is AUTOINCREMENT
[temp removeObjectForKey:#"id"];
[columns addObjectsFromArray:[temp allKeys]];
NSString *cols = [columns componentsJoinedByString:#","];
NSMutableString *colNames = [[NSMutableString alloc] initWithString:
[NSString stringWithFormat:#"INSERT INTO %s (",[table UTF8String]]];
[colNames appendString:cols];
[colNames appendString:#")"];
// Values for INSERT
[values addObjectsFromArray:[temp allValues] ];
// Prepare the ? marks for bindings
NSMutableArray * marks = [NSMutableArray new];
for (int i=0 ; i < [values count]; i++)
[marks addObject:#"?"];
NSMutableString *s = [[NSMutableString alloc] init];
NSMutableString *valNames = [[NSMutableString alloc] initWithString:#" VALUES ("];
[valNames appendString:[marks componentsJoinedByString:#","]];
[valNames appendString:#")"];
[colNames appendString:valNames];
const char *sql = [colNames UTF8String];
#ifdef DEBUG
// NSLog(#"avvDB writeDATA insert string %#",colNames);
#endif
if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK)
{
NSLog(#"DB:Problem with prepare statement write %s, data: %#",sqlite3_errmsg(db),data);
NSLog(#"DB:Query %#",colNames);
return 0;
}
// binding
for(int i = 0; i < [values count]; i++)
{
// According to the doc, if the 3rd value is a null pointer, the effect is similar to
// sqlite3_bind_null
if ([[values objectAtIndex:i] length] == 0 || [values objectAtIndex:i] == nil)
sqlite3_bind_text(sqlStatement, i+1, nil,-1, SQLITE_TRANSIENT);
else
{
[s setString:[NSString stringWithFormat:#"%#",[values objectAtIndex:i]]];
const char* currentValue = [s UTF8String];
sqlite3_bind_text(sqlStatement, i+1, currentValue,-1, SQLITE_TRANSIENT);
}
}
if (sqlite3_step(sqlStatement) == SQLITE_DONE) {
// NSLog(#"Location inserted on database");
}
else {
NSLog(#"DB:Error on write: %i %s %#",sqlite3_errcode(db),sqlite3_errmsg(db),data);
NSLog(#"DB:Query %#",colNames);
}
// if (sqlite3_exec(db, sql, NULL, NULL, NULL) == SQLITE_OK)
// {
// NSLog(#"Last id %llu %s",sqlite3_last_insert_rowid(db),sqlite3_errmsg(db));
// }
// NSLog(#"Ecexecuted write %#",colNames);
valNames = nil;
colNames = nil;
marks = nil;
s = nil;
}// end try
#catch(NSException* e)
{
NSLog(#"Eccezione in write %#",[e reason]);
}
#finally {
sqlite3_finalize(sqlStatement);
sqlStatement = nil;
return sqlite3_last_insert_rowid(db);
}
}
An empty string is not the same as NULL.
To set a parameter to NULL, use sqlite3_bind_null.
Alternatively, just don't mention empty parameters in the INSERT statement.
Related
Here is the code that using SQLite.
This function will be call from some threads.
I don't know well, following code is correct one or not.
There are lock/unlock code.
Please advice me whether I used lock/unlock correctly.
- (NSMutableArray*) Query:(NSString *)query_str forColumn:(NSString*)mycolumn
{
sqlite3_stmt *statement;
const char *query_stmt = [query_str UTF8String];
[dbLock lock];
if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) != SQLITE_OK) {
[dbLock unlock];
return nil;
}
NSMutableArray *queryResult = [NSMutableArray array];
while (sqlite3_step(statement) == SQLITE_ROW) {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
int num_of_fields = sqlite3_column_count(statement);
for (int i = 0; i < num_of_fields; i++) {
NSString * col_name = [[NSString alloc] initWithUTF8String:sqlite3_column_name(statement, i)];
const char * value = (const char *)sqlite3_column_text(statement, i);
NSString * col_text;
if (value)
col_text = [[NSString alloc] initWithUTF8String:value];
else
col_text = #"";
if (col_name && col_text) {
if ([col_text isEqualToString:#"--"] == NO && [col_text isEqualToString:#""] == NO)
[dict setObject:col_text forKey:col_name];
}
}
if (mycolumn == nil) {
[queryResult addObject:dict];
} else {
NSString *tmp = [dict objectForKey:mycolumn];
if (tmp != nil) {
[queryResult addObject:tmp];
}
}
}
sqlite3_finalize(statement);
[dbLock unlock];
return queryResult;
}
The locking is around all the database access functions, so as long as all the other database access code also does similar locking, the database is protected.
The query_str access is outside the lock, so it is not protected against concurrent modifications. Whether that could happen in your program is something only you can decide.
My question is how to insert a fetched JSON array data into sqlite database.
I have fetched JSON data which is an array of dictionaries.
My code to save JSON result looks like this:
-(BOOL) saveApiResults: (NSString *)tableName : (NSArray *)data
{
BOOL saveSuccess = NO;
#try {
const char *dbPath = [databasePath UTF8String];
if(sqlite3_open(dbPath,&database)==SQLITE_OK) {
sqlite3_exec(database, "BEGIN", 0, 0, 0);
//pass an array containing json dictionary to below line
NSDictionary *rowData=[data objectAtIndex:0];
NSArray *keyArray = [rowData allKeys];
NSLog(#"key array %#",keyArray);
NSString *insertSQL=#"INSERT INTO ";
insertSQL=[insertSQL stringByAppendingString:#"moodsdata"];
insertSQL=[insertSQL stringByAppendingString:#" VALUES("];
for(int j=0;j<[keyArray count];j++)
{
insertSQL=[insertSQL stringByAppendingString:#"?"];
if(j<[keyArray count]-1)
insertSQL=[insertSQL stringByAppendingString:#","];
}
insertSQL=[insertSQL stringByAppendingString:#");"];
NSLog(#"query : %# ",insertSQL);
const char *sqlstatement = [insertSQL UTF8String];
sqlite3_stmt *compiledstatement;
if(sqlite3_prepare_v2(database,sqlstatement , -1, &compiledstatement, NULL)==SQLITE_OK) {
for (NSUInteger i = 0; i < [data count]; i++) {
NSDictionary *rowData=[data objectAtIndex:0];
for(int j=0;j<[keyArray count];j++) {
NSString *val = #"";
NSString *value=(NSString *)[rowData objectForKey:[keyArray objectAtIndex:j]];
if((value != nil) && (![value isEqual:[NSNull null]]))
val=[NSString stringWithFormat:#"%#",value];
NSLog(#"values %#",val);
sqlite3_bind_text(compiledstatement,j+1,[val UTF8String], -1, SQLITE_TRANSIENT);
}
if(sqlite3_step(compiledstatement) != SQLITE_DONE) {
NSLog(#"ERROR");
}
sqlite3_clear_bindings(compiledstatement);
sqlite3_reset(compiledstatement);
}
sqlite3_exec(database, "COMMIT", 0, 0, 0);
saveSuccess = YES;
NSLog(#"RESULTS SAVED SUCCESSFULLY!");
} else {
NSLog(#"StatemenT FAILED (%s)", sqlite3_errmsg(database));
}
sqlite3_finalize(compiledstatement);
} else {
NSLog(#"Statement FAILED (%s)", sqlite3_errmsg(database));
}
}
#catch (NSException *exception) {
NSLog(#"NSException : %#",exception.description);
}
#finally {
sqlite3_close(database);
}
return saveSuccess;
}
My question is when I try to pass the JSON array to this method it only saves the value for first array object . i.e. only first dictionary values get saved. Please tell me what I am doing wrong.
You primary issue is that you mistakenly access data[0] instead of data[i] in your loop that does the actual inserting.
But there are lots of other little issues with this code. Here's your code all cleaned up:
-(BOOL) saveApiResults:(NSString *)tableName data:(NSArray *)data
{
BOOL saveSuccess = NO;
const char *dbPath = [databasePath UTF8String];
if (sqlite3_open(dbPath,&database) == SQLITE_OK) {
sqlite3_exec(database, "BEGIN", 0, 0, 0);
//pass an array containing json dictionary to below line
NSDictionary *rowData = data[0];
NSArray *keyArray = [rowData allKeys];
NSLog(#"key array %#",keyArray);
NSMutableString *insertSQL = #"INSERT INTO moods data VALUES(";
for (NSInteger j = 0; j < [keyArray count]; j++)
{
if (j) {
[insertSQL appendString:#","];
}
[insertSQL appendString:#"?"];
}
[insertSQL appendString:#");"];
NSLog(#"query : %# ",insertSQL);
const char *sqlstatement = [insertSQL UTF8String];
sqlite3_stmt *compiledstatement;
if (sqlite3_prepare_v2(database, sqlstatement, -1, &compiledstatement, NULL) == SQLITE_OK) {
for (NSDictionary *rowData in data) {
for (NSInteger j = 0; j < [keyArray count]; j++) {
NSString *val = #"";
NSString *value = rowData[keyArray[j];
if (value && ![value isEqual:[NSNull null]]) {
val = [NSString stringWithFormat:#"%#",value];
}
NSLog(#"values %#",val);
sqlite3_bind_text(compiled statement, j + 1, [val UTF8String], -1, SQLITE_TRANSIENT);
}
if (sqlite3_step(compiledstatement) != SQLITE_DONE) {
NSLog(#"ERROR");
}
sqlite3_reset(compiledstatement);
}
sqlite3_exec(database, "COMMIT", 0, 0, 0);
sqlite3_finalize(compiledstatement);
saveSuccess = YES;
NSLog(#"RESULTS SAVED SUCCESSFULLY!");
} else {
NSLog(#"StatemenTtFAILED (%s)", sqlite3_errmsg(database));
}
sqlite3_close(database);
} else {
NSLog(#"Statement FAILED (%s)", sqlite3_errmsg(database));
}
return saveSuccess;
}
There is no need for try/catch. Just do proper error checking.
Use NSMutableString to build up a string piece by piece.
Only finalize a statement if you successfully prepare it.
Be sure to close a database if you open it.
Use modern syntax for accessing values from dictionaries and arrays. It's easier to read and type.
Use modern loops when possible.
Use whitespace. It makes the code easier to read.
Give all of your method parameters a name.
But your SQLite code was actually better than most posts here. You actually use sqlite_bind_xxx to bind values to your query. Too few people do that.
In your code where you're grabbing the values, you are grabbing the row data with:
NSDictionary *rowData=[data objectAtIndex:0];
I suspect you mean:
NSDictionary *rowData=[data objectAtIndex:i];
This is the last row inserted method that return me the last id.
Code:
-(int)LastId {
int noteID;
NSString * sqlStr;
sqlStr = [NSString stringWithFormat:#"select * from notes"];
sqlite3_stmt *ReturnStatement = (sqlite3_stmt *) [self getStatement: sqlStr];
while(sqlite3_step(ReturnStatement) == SQLITE_ROW){
#try{
noteID=[[NSString stringWithUTF8String:(char *)sqlite3_column_text(ReturnStatement, 0)] intValue];
} #catch (NSException *ept) {
NSLog(#"Exception in Method: '%#', Reason: %#", #"loadData", [ept reason]);
}
}
return noteID;
}
This is the method that i inserted the last return id
-(void)AddNoteImages1:(NSString *)imageName1 andID:(int)notesID{
notesID = [self LastId];
NSString *query = [NSString stringWithFormat:#"insert into image1(image1Name,notes_id) values('%#','%d')",imageName1,notesID];
[self InsUpdateDelData:query];
}
This is the method that i want to get the last inserted row id
-(NSMutableArray *)loadImages1:(int)note_ID{
NSMutableArray *dataArray =[[NSMutableArray alloc] init];
NSString * sqlStr = [NSString stringWithFormat:#"SELECT * FROM image1 WHERE notes_id = %d",note_ID];
sqlite3_stmt *ReturnStatement = (sqlite3_stmt *) [self getStatement: sqlStr];
while(sqlite3_step(ReturnStatement) == SQLITE_ROW){
#try{
image1DC *imageData = [[image1DC alloc] init];
imageData.image1ID = [[NSString stringWithUTF8String:(char *)sqlite3_column_text(ReturnStatement, 0)] intValue];
imageData.image1Name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(ReturnStatement, 1)];
[dataArray addObject:imageData];
} #catch (NSException *ept) {
NSLog(#"Exception in %s, Reason: %#", __PRETTY_FUNCTION__, [ept reason]);
}
}
return dataArray;
}
The LastId function just steps through all rows in the table, without any specific order; it will return some random ID.
To get the last ID inserted in the same database connection, call the sqlite3_last_insert_rowid() function.
To get the ID with the largest value, execute SELECT MAX(ID) FROM ....
There are two ways of doing this one
Using the sqlite3_last_insert_rowid() method of sqlite
Using the MAX(your_auto_Incremental_primary_key_variable)
What i generally do is club the sqlite3_last_insert_rowid() call with an insert statement to get the last inserted rowID
- (BOOL)insertItem{
const char *query = "your insert statement";
sqlite3_stmt *sqlstatement = nil;
if (sqlite3_prepare_v2(dbreference, query, -1, &sqlstatement, NULL)==SQLITE_OK) {
//Your insert code here
float rowID = sqlite3_last_insert_rowid(dbreference);
NSLog(#"Last inserted row id = %.0f",rowID);
sqlite3_close(dbreference);
}
return YES;
}
where dbreference is a variable of type sqlite3
If the above solution is not suitable for your case then you can go with the Max(ID) approach here which would accepts the column name where you have stored your primary key which is auto incremental
- (int)getLastItemID{
const char *query = "select MAX(userid) from SampleTable";
sqlite3_stmt *sqlstatement = nil;
if (sqlite3_prepare_v2(dbreference, query, -1, &sqlstatement, NULL)==SQLITE_OK) {
while (sqlite3_step(sqlstatement)==SQLITE_ROW) {
int lastInsertedPrimaryKey = sqlite3_column_int(sqlstatement, 0);
return lastInsertedPrimaryKey;
}
sqlite3_close(dbreference);
}
return 0;
}
I have a table in a database which I access like this:
+(NSMutableArray *) queryDatabaseWithSQL:(NSString *)sql params:(NSArray *)params {
sqlite3 *database;
NSMutableArray *rtn = [[NSMutableArray alloc] init];
int index = 0;
NSString *filepath = [self copyDatabaseToDocumentsWithName:#"database"];
if (sqlite3_open([filepath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = [sql cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *compiledStatement;
if (sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
if ([params count]) {
for (int i = 0; i < [params count]; i++) {
NSString *obj = [params objectAtIndex:i];
sqlite3_bind_blob(compiledStatement, i + 1, [obj UTF8String], -1, SQLITE_TRANSIENT);
}
}
while (sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSMutableArray *arr = [[NSMutableArray alloc] init];
index = 0;
const char *s = (char *)sqlite3_column_text(compiledStatement, index);
while (s) {
[arr addObject:[NSString stringWithUTF8String:s]];
index++;
s = (char *)sqlite3_column_text(compiledStatement, index);
}
if (![rtn containsObject:arr]) {
[rtn addObject:arr];
}
}
}
sqlite3_finalize(compiledStatement);
}
NSLog(#"ERROR: %s", sqlite3_errmsg(database));
sqlite3_close(database);
return rtn;
}
This works fine when I call the function like this:
NSLog(#"%#", [database queryDatabaseWithSQL:#"SELECT * FROM FRIENDSUSERS WHERE USER = ?" params:#[[delegate->user objectForKey:#"username"]]]);
Then when I call the function using a string like this it doesn't return any rows:
NSLog(#"%#", [database queryDatabaseWithSQL:#"SELECT * FROM FRIENDSUSERS WHERE USER = ?" params:#[#"username"]]);
I haven't got a clue what is going one with but I have checked the strings match and they do so I'm now stuck
Can anyone see any reason why this would not work
I have run error checks as well and every time it returns no error, or rows :(
Thanks in advance
So I'm fairly new to iOS development and I'm having problems with my select function. I made a function that should take in a select query and the table name and return an array of results where each array entry is a dictionary with a row of results. Somehow my query for column names is deleting my columnNames variable and returning crazy results. I'm just trying to figure out an easy way to store, access, manipulate query results
Here is the function that converts results into array:
-(NSMutableArray *)selectQuery:(NSString*)query
table:(NSString*)table
{
NSMutableArray *returnArray = [NSMutableArray new];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSMutableArray *columnNames;
NSString *tableQuery = [NSString stringWithFormat:#"PRAGMA table_info('%#')", table];
if (sqlite3_prepare_v2(database, [tableQuery UTF8String], -1, &statement, nil) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
[columnNames addObject:[NSString stringWithUTF8String:(const char*)sqlite3_column_text(statement, 1)]];
}
}
else
{
NSLog(#"Error preparing table query:");
NSLog(tableQuery);
}
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK)
{
while(sqlite3_step(statement)==SQLITE_ROW)
{
NSMutableDictionary *temp= [NSMutableDictionary new];
for (int i = 0; i < [columnNames count]; i++)
{
[temp setObject:[NSString stringWithUTF8String:(const char*)sqlite3_column_text(statement, i)] forKey:columnNames[i]];
}
if (temp != nil)
{
[returnArray addObject:temp];
temp = nil;
}
}
sqlite3_reset(statement);
sqlite3_close(database);
}
else
{
NSLog(#"Error preparing select statement with query:");
NSLog(query);
}
}
else
{
NSLog(#"Could not open database");
}
return returnArray;
}
and heres the call to it
NSMutableArray *queryResults = [dbInstance selectQuery:[NSString stringWithFormat:#"SELECT gallons, mileage FROM fillups WHERE carId = \"%d\" ORDER BY date asc",
carId]
table:#"fillups"];
You are never instantiating columnNames. Thus, your attempt to add column names to that array will not succeed. To remedy this, when you declare it, you want to instantiate the mutable array object, too:
NSMutableArray *columnNames = [NSMutableArray array];
Unrelated to this problem, when you're done retrieving the column names, before you prepare your second statement, don't forget to release the memory associated with the first prepared statement:
sqlite3_finalize(statement);
Finally, when you're done retrieving the second prepared SQL statement, rather than calling sqlite3_reset, you want to call sqlite3_finalize again for that second prepared statement. The sqlite3_reset is used to reset a statement when you want to bind new values to ? placeholders in a statement, which is not applicable here, so no sqlite3_reset is needed. But if you don't call sqlite3_finalize, you're not releasing the memory associated with the prepared statement.
By the way, if you wanted to dynamically retrieve the column names and column types (without having to do PRAGMA table_info), you could do something like:
int rc;
if ((rc = sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, NULL)) != SQLITE_OK) {
NSLog(#"select failed %d: %s", rc, sqlite3_errmsg(database));
}
NSMutableArray *returnArray = [NSMutableArray array];
NSInteger columnCount = sqlite3_column_count(statement);
id value;
while ((rc = sqlite3_step(statement)) == SQLITE_ROW) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
for (NSInteger i = 0; i < columnCount; i++) {
NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(statement, i)];
switch (sqlite3_column_type(statement, i)) {
case SQLITE_NULL:
value = [NSNull null];
break;
case SQLITE_TEXT:
value = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement, i)];
break;
case SQLITE_INTEGER:
value = #(sqlite3_column_int64(statement, i));
break;
case SQLITE_FLOAT:
value = #(sqlite3_column_double(statement, i));
break;
case SQLITE_BLOB:
{
NSInteger length = sqlite3_column_bytes(statement, i);
const void *bytes = sqlite3_column_blob(statement, i);
value = [NSData dataWithBytes:bytes length:length];
break;
}
default:
NSLog(#"unknown column type");
value = [NSNull null];
break;
}
dictionary[columnName] = value;
}
[returnArray addObject:dictionary];
}
if (rc != SQLITE_DONE) {
NSLog(#"error returning results %d %s", rc, sqlite3_errmsg(database));
}
sqlite3_finalize(statement);