SQLITE (fmdb) select where x is null not working - ios

I'm using FMDB on iOS, attempting to query a table with a statement such as,
select * from test where foo isNull
In the sqlite C API, this ends up binding to null using this API,
int sqlite3_bind_null(sqlite3_stmt*, int);
This isn't working. The select invoked through fmdb which seems to be correctly binding the column to null is not finding matching records.
If in the sqlite3 command line session I do the following command, it works, but not through the sqlite C API via FMDB.
select * from test where foo isNull;
Here's fmdb code which reproduces the problem. It looks like fmdb is doing the proper steps invoking sqlite3_bind_null.
NSString *stmt = #"select * from test where shipToCode=:foo";
NSDictionary *dict = #{#"foo" : [NSNull null]};
FMResultSet *rs = [db executeQuery:stmt withParameterDictionary:dict];
int count = 0;
while ([rs next]) {
count++;
}
NSLog(#"Count %d", count);

NULL cannot be compared with the = operator; it is not equal with any value, even itself.
To compare with NULL, you have to use the IS NULL operator:
stmt = #"select * from test where shipToCode is null";

Try the following and check if there are any changes:
NSString *query = #"SELECT * FROM test WHERE shipToCode is NULL";
FMResultSet *rs = [db executeQuery:query];
int count = 0;
while ([rs next]) {
count++;
}
NSLog(#"Count %d", count);

Related

How to execute multiple select statement in one query in sqlite in iOS?

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

Inserting into sqlite table

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.

Rows order in SQLite Database (iOS)

I have a database with a table called 'connection', for simplicities' sake, let's say I only have one column which is called 'rowName'. Now let's say I add a row with rowName = a; now I add a row with rowName = q, and lastly I add a row with rowName = w (letters are completely random). Now, I irritate thru the results with the statement:
NSString * queryStatements = [NSString stringWithFormat:#"SELECT rowName, FROM tableName"];
and using the code:
NSMutableArray * rows = [[NSMutableArray alloc] init]; //create a new array
sqlite3_stmt * statement;
if(sqlite3_prepare_v2(databaseHandle, [queryStatements UTF8String], -1, &statement, NULL) == SQLITE_OK){
while (sqlite3_step(statement) == SQLITE_ROW){
NSString * rowName = [NSString stringWithUTF8String : (char*) sqlite_column_text(statement, 1)];
[rows addObject : connection];
} sqlite3_finalize(statement_;
}
In the array rows, will the object at index 0 be rowName = a, and at index 1 rowName=q, and at index 2 rowName = w? or will it be random? Is there a way to make it not-random?
Also, if i delete a row, will it have any affect on the other rows order?
Never depend on a sort order from your database. Always specify one if it is required.
SELECT rowName FROM tableName order by rowName
gives you the data sorted by rowName. If you need a different order, you need another column.
You can also sort your NSArray if need be.
What sort order are you looking for?

Passing an array to sqlite WHERE IN clause via FMDB?

Is it possible to pass an array to a SELECT … WHERE … IN statement via FMDB?
I tried to implode the array like this:
NSArray *mergeIds; // An array with NSNumber Objects
NSString *mergeIdString = [mergeIds componentsJoinedByString:#","];
NSString *query = #"SELECT * FROM items WHERE last_merge_id IN (?)";
FMResultSet *result = [database executeQuery:query, mergeIdString];
This only works if there is exactly 1 object in the array, which leads me to believe that FMDB adds quotes around the whole imploded string.
So I tried passing the array as is to FMDB's method:
NSArray *mergeIds; // An array with NSNumber Objects
NSString *query = #"SELECT * FROM items WHERE last_merge_id IN (?)";
FMResultSet *result = [database executeQuery:query, mergeIds];
Which doesn't work at all.
I didn't find anything about it in the README or the samples on FMDB's github page.
Thanks, Stefan
I was having the same issue, and I figured it out, at least for my own app. First, structure your query like so, matching the number of question marks as the amount of data in the array:
NSString *getDataSql = #"SELECT * FROM data WHERE dataID IN (?, ?, ?)";
Then use the executeQuery:withArgumentsInArray call:
FMResultSet *results = [database executeQuery:getDataSql withArgumentsInArray:dataIDs];
In my case, I had an array of NSString objects inside the NSArray named dataIDs. I tried all sorts of things to get this SQL query working, and finally with this combination of sql / function call, I was able to get proper results.
Well I guess I have to use executeQueryWithFormat (which, according to FMDB documentation is not the recommended way). Anyway, here's my solution:
NSArray *mergeIds; // An array of NSNumber Objects
NSString *mergeIdString = [mergeIds componentsJoinedByString:#","];
NSString *query = #"SELECT * FROM items WHERE last_merge_id IN (?)";
FMResultSet *res = [self.database executeQueryWithFormat:query, mergeIdString];
Adding onto Wayne Liu, if you know that the strings do not contain single or double quotes, you could simply do:
NSString * delimitedString = [strArray componentsJoinedByString:#"','"];
NSString * sql = [NSString stringWithFormat:#"SELECT * FROM tableName WHERE fieldName IN ('%#')", delimitedString];
If the keys are strings, I use the following code to generate the SQL command:
(assume strArray is an NSArray containing NSString elements)
NSString * strComma = [strArray componentsJoinedByString:#"\", \""];
NSString * sql = [NSString stringWithFormat:#"SELECT * FROM tableName WHERE fieldName IN (\"%#\")", strComma];
Please note: if any elements in strArray could potentially contain the "double quote" symbols, you need to write extra codes (before these 2 lines) to escape them by writing 2 double quotes instead.
I created a simple FMDB extension to solve the problem:
FMDB+InOperator on GitHub
Here's a Swift extension for FMDatabase that breaks array query parameters into multiple named parameters.
extension FMDatabase {
func executeQuery(query: String, params:[String: AnyObject]) -> FMResultSet? {
var q = query
var d = [String: AnyObject]()
for (key, val) in params {
if let arr = val as? [AnyObject] {
var r = [String]()
for var i = 0; i < arr.count; i++ {
let keyWithIndex = "\(key)_\(i)"
r.append(":\(keyWithIndex)")
d[keyWithIndex] = arr[i]
}
let replacement = ",".join(r)
q = q.stringByReplacingOccurrencesOfString(":\(key)", withString: "(\(replacement))", options: NSStringCompareOptions.LiteralSearch, range: nil)
}
else {
d[key] = val
}
}
return executeQuery(q, withParameterDictionary: d)
}
}
Example:
let sql = "SELECT * FROM things WHERE id IN :thing_ids"
let rs = db.executQuery(sql, params: ["thing_ids": [1, 2, 3]])

Multiple Queries not Running in FMDB

I'm using FMDB to create a SQLite database on iPhone. I have a initial.sql that is of the form
CREATE TABLE Abc ... ;
CREATE TABLE Def ... ;
I load this by loading the file into an NSString and running it
NSString * str = // string from file initial.sql
[db executeUpdate: str];
This succeeds but later on I get a failure:
no such table: Def
It's clear that the second statement is not being called. How can I do this so that all of the queries will be called?
According to the SQLite documentation:
"The routines sqlite3_prepare_v2(), sqlite3_prepare(), sqlite3_prepare16(), sqlite3_prepare16_v2(), sqlite3_exec(), and sqlite3_get_table() accept an SQL statement list (sql-stmt-list) which is a semicolon-separated list of statements."
So, this should all work.
I got bitten by this one too; it took me an entire morning of stepping through FMDatabase and reading the sqlite3 API documentation to find it. I am still not entirely sure about the root cause of the issue, but according to this bug in PHP, it is necessary to call sqlite3_exec instead of preparing the statement with sqlite3_prepare_v2 and then calling sqlite3_step.
The documentation does not seem to suggest that this behaviour would happen, hence our confusion, and I would love for someone with more experience with sqlite to come forward with some hypotheses.
I solved this by developing a method to execute a batch of queries. Please find the code below. If you prefer, you could rewrite this into a category instead of just adding it to FMDatabase.h, your call.
Add this to the FMDatabase interface in FMDatabase.h:
- (BOOL)executeBatch:(NSString*)sql error:(NSError**)error;
Add this to the FMDatabase implementation in FMDatabase.m:
- (BOOL)executeBatch:(NSString *)sql error:(NSError**)error
{
char* errorOutput;
int responseCode = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &errorOutput);
if (errorOutput != nil)
{
*error = [NSError errorWithDomain:[NSString stringWithUTF8String:errorOutput]
code:responseCode
userInfo:nil];
return false;
}
return true;
}
Please note that there are many features missing from executeBatch which make it unsuitable for a lot of purposes. Specifically, it doesn't check to see if the database is locked, it doesn't make sure FMDatabase itself isn't locked, it doesn't support statement caching.
If you need that, the above is a good starting point to code it yourself. Happy hacking!
FMDB v2.3 now has a native wrapper for sqlite3_exec called executeStatements:
BOOL success;
NSString *sql = #"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
It also has a variant that employs the sqlite3_exec callback, implemented as a block:
sql = #"select count(*) as count from bulktest1;"
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[#"count"] integerValue];
NSLog(#"Count = %d", count);
return 0; // if you return 0, it continues execution; return non-zero, it stops execution
}];
Split Batch Statement
Add in .h file:
#import "FMSQLStatementSplitter.h"
#import "FMDatabaseQueue.h"
FMSQLStatementSplitter can split batch sql statement into several separated statements, then [FMDatabase executeUpdate:] or other methods can be used to execute each separated statement:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:databasePath];
NSString *batchStatement = #"insert into ftest values ('hello;');"
#"insert into ftest values ('hi;');"
#"insert into ftest values ('not h!\\\\');"
#"insert into ftest values ('definitely not h!')";
NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
[queue inDatabase:^(FMDatabase *adb) {
for (FMSplittedStatement *sqlittedStatement in statements)
{
[adb executeUpdate:sqlittedStatement.statementString];
}
}];

Resources