While building a Search for my app i ran into a problem whilst using the FMDB SQLite Wrapper (https://github.com/ccgus/fmdb).
When I search my database with this SQL Command, everything is fine. 13 objects are returned and I can use them.
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE '%Daimler%'"];
But when i try to insert the searchQuery from the User Input like this:
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE (?)", theSearchQuery];
... the value is dont be inserted into SQL Command. And I dont get any returned objects from the DB. even if the String (theSearchQuery) is the same written in the first example.
Additionaly I post a part from the documentation of FMDB for your convinience. :)
Data Sanitization
When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion. Instead, you should use the standard SQLite binding syntax:
INSERT INTO myTable VALUES (?, ?, ?)
The ? character is recognized by SQLite as a placeholder for a value to be inserted. The execution methods all accept a variable number of arguments (or a representation of those arguments, such as an NSArray or a va_list), which are properly escaped for you.
Thus, you SHOULD NOT do this (or anything like this):
[db executeUpdate:[NSString stringWithFormat:#"INSERT INTO myTable VALUES (%#)", #"this has \" lots of ' bizarre \" quotes '"]];
Instead, you SHOULD do:
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", #"this has \" lots of ' bizarre \" quotes '"];
All arguments provided to the -executeUpdate: method (or any of the variants that accept a va_list as a parameter) must be objects. The following will not work (and will result in a crash):
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", 42];
The proper way to insert a number is to box it in an NSNumber object:
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];
Alternatively, you can use the -execute*WithFormat: variant to use NSString-style substitution:
[db executeUpdateWithFormat:#"INSERT INTO myTable VALUES (%d)", 42];
Internally, the -execute*WithFormat: methods are properly boxing things for you. The following percent modifiers are recognized: %#, %c, %s, %d, %D, %i, %u, %U, %hi, %hu, %qi, %qu, %f, %g, %ld, %lu, %lld, and %llu. Using a modifier other than those will have unpredictable results. If, for some reason, you need the % character to appear in your SQL statement, you should use %%.
NSString *search_text = [NSString stringWithFormat:#"%%%#%%", theSearchQuery];
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE ?", search_text];
I would highly recommend to avoid creating queries with stringWithFormat:! There is a good reason why FMDB tries to force you to use their data sanitization. However, since FMDB is boxing your input, surrounding parenthesis in the following code are not needed and may cause your problem.
[db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE (?)", theSearchQuery];
Simple add arguments without any parenthisis because you never know how FMDB boxes your argument internally.
[db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE ?", theSearchQuery];
If this still doesn't work try to use the suggested executeQueryWithFormat: method of FMDB:
[db executeQueryWithFormat:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE %#", theSearchQuery];
Related
NSString *query = #"select * from 'topic' where 'group' = ? order by lastUpdatedAtFloat desc";
FMResultSet * rs = [db executeQuery:query, groupUuid];
while ([rs next]) {
NSLog(#"show");
}
This is the query that I used, but no result is found while I am sure my database tables have data inside.
I know that FMDB query has quite some questions similar to my case, but I have not seen any reported correct demo or document for the best practice to choose which query method in the following to use for different scenarios.
- (FMResultSet *)executeQuery:(NSString *)sql;
- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments;
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
Can anyone point me a way out?
I want to get grouped data from a table in SQLite. For example, the table is like below:
NSString *querySQL=[[NSString alloc]initWithFormat:#"select * from %# where company like '%#%#' and sepll||first_spell||phone||name like '%#%#%#' limit %d offset %d",TABLE_NAME,company,#"%",#"%",py,#"%",pageSize,(page-1)*pageSize];
FMResultSet *rs = [[dbManager getDatabase] executeQuery:querySQL];
My question is,after executeQuery,how can I group by id ascending about rs again?
Any help is appreciated.
You should use "Group By" clause. GROUP BY clause is used in collaboration with the SELECT statement to arrange identical data into groups.
NSString *query = [NSString stringWithString:#"SELECT Row_Name1, Row_Name2 FROM Table_Name where Row_Name1 like '%%?%%' or Row_Name2 like '%%?%%' group by Row_Name1, Row_Name2 order by Row_YouWantToGroup"];
FMResultSet *rs = [theDatabase executeQuery:query];
I wrote a query with arguments in FMDB using ? mark symbol.I want to get details of users who has any of info in the list(hope i can give different info separated by commas in "in" statement in sql). Since my arguments having special symbols, It is throwing error. How to escape there special symbols. I tried different methods but none worked yet
My code is like:
FMResultSet *results = [db executeQuery:#"select details from user where info in(?)",infoList];
while([results next])
{...}
Info is a string combined by different string seperated by commas.For example:
'C-note','Cuban missile crisis, the','cubbyhole','I Love Lucy','I'm a Celebrity ... Get me Out of Here!','Iacocca, Lee','-iana','Ivy League, the'
Thanks in advance
You can't use a single ? with an in clause unless you only bind a single value. It doesn't work for a list of values.
Since infoList is an array of string values, one option is to add a ? for each value in the list.
NSMutableString *query = [NSMutableString stringWithString:#"select details from user where info in ("];
for (NSInteger i = 0; i < infoList.count; i++) {
if (i) {
[query appendString:#","];
}
[query appendString:#"?"];
}
[query appendString:#")"];
FMResultSet *results = [db executeQuery:query withArgumentsInArray:infoList];
I am creating a temp table using the following SQL query. But its not showing any results (result set is empty) when I execute it programmatically, and shows records when I execute it manually on the database.
NSString *query=[NSString stringWithFormat:#"create temp table search2 as select Observationsid from Observations where admin_id=%d AND teacher_id=%d AND observation_type=%d AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'",appDelegate.admin_id,appDelegate.teacher_id,appDelegate.observationType];
FMResultSet *results=[appDelegate.database executeQuery:query];
Nslogged query:
Printing description of results->_query:
create temp table search2 as select Observationsid from Observations where admin_id=2 AND teacher_id=1 AND observation_type=2 AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'
and records are there in database.
This SQL statement shouldn't return results, so you should use executeUpdate method rather than a executeQuery method. You should not see any results from this. If you want to see the results, do a separate executeQuery statement returning the results from search2. (I assume you're building the search2 table for a reason.)
NSString *query=[NSString stringWithFormat:#"create temp table search2 as select Observationsid from Observations where admin_id=%d AND teacher_id=%d AND observation_type=%d AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'",appDelegate.admin_id,appDelegate.teacher_id,appDelegate.observationType];
if (![appDelegate.database executeUpdate:query])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
query = #"SELECT * FROM search2";
FMResultSet *results = [appDelegate.database executeQuery:query];
if (!results)
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
By the way, while you can get away with stringWithFormat for this particular SQL, that's not a good idea. You generally want to use the ? placeholders, e.g.
NSString *query = #"create temp table search2 as select Observationsid from Observations where admin_id=? AND teacher_id=? AND observation_type=? AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'";
if (![appDelegate.database executeUpdate:query, #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType)])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
Unrelated, but I notice that you're using MM-DD-YYYY format text string for your dates. That's not going to work. SQLite doesn't natively "understand" dates. Notably, if you've stored your dates in MM-DD-YYYY format, a text string of 12-21-2052 is BETWEEN '12-20-2013' AND '12-23-2013' (because text strings are compared in alphabetically order), which is probably not what you intended. See Date And Time Functions.
But, fortunately, FMDB does the NSDate conversion for you, storing it as numeric value. So, for example, let's assume your table was defined as follows:
CREATE TABLE IF NOT EXISTS Observations
(Observationsid INTEGER PRIMARY KEY AUTOINCREMENT,
admin_id INTEGER,
teacher_id INTEGER,
observation_type INTEGER,
isvalid TEXT,
date REAL);
You really want to use YYYY-MM-DD or, better, rely upon FMDatabase to convert your NSDate values for you. Remember, SQLite does not have proper date formats, so choose an appropriate format (e.g. YYYY-MM-DD format) or let FMDatabase handle this for you, using NSDate objects.
If you want to facilitate the conversion of date strings to dates, you can use a NSDateFormatter, e.g.:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"MM-dd-yyyy";
NSDate *someDate = [formatter dateFromString:#"12-22-2013"];
if (![appDelegate.database executeUpdate:#"INSERT INTO Observations (admin_id, teacher_id, observation_type, isvalid, date) VALUES (?, ?, ?, ?, ?)", #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType), #"yes", someDate])
NSLog(#"insert error: %#", [appDelegate.database lastErrorMessage]);
Or, if I wanted to select the dates between these two dates:
NSDate *startDate = [formatter dateFromString:#"12-20-2013"];
NSDate *endDate = [formatter dateFromString:#"12-23-2013"];
NSString *query = #"create temp table search2 as select Observationsid, date from Observations where admin_id=? AND teacher_id=? AND observation_type=? AND isvalid='yes' AND date BETWEEN ? AND ?";
if (![appDelegate.database executeUpdate:query, #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType), startDate, endDate])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
query = #"SELECT * FROM search2";
FMResultSet *results = [appDelegate.database executeQuery:query];
if (!results)
NSLog(#"select error: %#", [appDelegate.database lastErrorMessage]);
while ([results next])
NSLog(#"%d %#", [results intForColumnIndex:0], [results dateForColumnIndex:1]);
If you want to avoid this NSDate logic, you could alternatively consciously store dates in a text format in YYYY-MM-DD format. That works, too. Again, see that Date and Time Functions page for a list of valid text formats for dates.
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];
}
}];