I have a sqlite statement that provides me with a selected single row and 20 columns.
Up to now I've been using this while loop:
while(sqlite3_step(statement) == SQLITE_ROW) {
NSString *name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, an_incrementing_int)];
...
}
However the problem with this is as there is only one row it will naturally only return the 1st column.
So is there something like while.. == SQLITE_COLUMN ?
Thanks
To get number of column a query returns, use sqlite3_column_count.
Function to return column data, sqlite3_column_... all accept an 2nd argument which is int column index.
NSString coldata;
int i;
while(sqlite3_step(statement) == SQLITE_ROW) {
for (i=0; i<sqlite3_column_count(statement); ++i) {
coldata= [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)];
}
}
Note also: take care using data pointer to column values!
The pointers returned are valid until a type conversion occurs as
described above, or until sqlite3_step() or sqlite3_reset() or
sqlite3_finalize() is called. The memory space used to hold strings
and BLOBs is freed automatically. Do not pass the pointers returned
from sqlite3_column_blob(), sqlite3_column_text(), etc. into
sqlite3_free().
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
So I created an NSObject that connects to an SQLite Database and everything was working perfectly. In my code, if the query is executable, the code goes through a series of conditions and executes the query. Then, for no reason and I hadn't done anything, the executable queries stopped working. I tried deleting the db file and copying it back again (I made sure to check all the checkboxes), but all that happened was that the project.pbxproj suddenly appeared in all my files and Xcode still couldn't execute my executable queries. I put the project.pbxproj back in the project file and kept deleting/recopying the db file from/in the project without luck.
Here is my code, I know it is perfectly fine because I changed nothing in it and it was working:
-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{
// Create a sqlite object.
sqlite3 *sqlite3Database;
// Set the database file path.
NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename];
// Initialize the results array.
if (self.arrResults != nil) {
[self.arrResults removeAllObjects];
self.arrResults = nil;
}
self.arrResults = [[NSMutableArray alloc] init];
// Initialize the column names array.
if (self.arrColumnNames != nil) {
[self.arrColumnNames removeAllObjects];
self.arrColumnNames = nil;
}
self.arrColumnNames = [[NSMutableArray alloc] init];
// Open the database.
BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
if(openDatabaseResult == SQLITE_OK) {
// Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement.
sqlite3_stmt *compiledStatement;
// Load all data from database to memory.
BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
if(prepareStatementResult == SQLITE_OK) {
// Check if the query is non-executable.
if (!queryExecutable){
// In this case data must be loaded from the database.
// Declare an array to keep the data for each fetched row.
NSMutableArray *arrDataRow;
// Loop through the results and add them to the results array row by row.
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Initialize the mutable array that will contain the data of a fetched row.
arrDataRow = [[NSMutableArray alloc] init];
// Get the total number of columns.
int totalColumns = sqlite3_column_count(compiledStatement);
// Go through all columns and fetch each column data.
for (int i=0; i<totalColumns; i++){
// Convert the column data to text (characters).
char *dbDataAsChars = (char *)sqlite3_column_text(compiledStatement, i);
// If there are contents in the currenct column (field) then add them to the current row array.
if (dbDataAsChars != NULL) {
// Convert the characters to string.
[arrDataRow addObject:[NSString stringWithUTF8String:dbDataAsChars]];
}
// Keep the current column name.
if (self.arrColumnNames.count != totalColumns) {
dbDataAsChars = (char *)sqlite3_column_name(compiledStatement, i);
[self.arrColumnNames addObject:[NSString stringWithUTF8String:dbDataAsChars]];
}
}
// Store each fetched data row in the results array, but first check if there is actually data.
if (arrDataRow.count > 0) {
[self.arrResults addObject:arrDataRow];
}
}
}
else {
// This is the case of an executable query (insert, update, ...).
// Execute the query.
BOOL executeQueryResults = sqlite3_step(compiledStatement);
if (executeQueryResults == SQLITE_DONE) {
// Keep the affected rows.
self.affectedRows = sqlite3_changes(sqlite3Database);
// Keep the last inserted row ID.
self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
}else if(executeQueryResults == SQLITE_BUSY){
NSLog(#"busy");
}else if(executeQueryResults == SQLITE_ERROR){
NSLog(#"error in exec");
}else if(executeQueryResults == SQLITE_MISUSE){
NSLog(#"misuse");
}else if(executeQueryResults == SQLITE_ROW){
NSLog(#"row");
}
else {
// If could not execute the query show the error message on the debugger.
NSLog(#"DB Error: %s", sqlite3_errmsg(sqlite3Database));
}
}
}
else {
// In the database cannot be opened then show the error message on the debugger.
NSLog(#"%s", sqlite3_errmsg(sqlite3Database));
}
// Release the compiled statement from memory.
sqlite3_finalize(compiledStatement);
}
// Close the database.
sqlite3_close(sqlite3Database);
}
I created all the conditions to check what kind of problem there was, turns out executeQueryResults is equal to SQLITE_ERROR, and the error is: unkown error.
Also, I tried logging the arrays and they are either empty or equal to null even when the database is not empty to start with.
Also, I am getting an error (ONLY IN THIS PROJECT) at all lines comparing executeQueryResults to SQLITE_DONE/BUSY/ERROR etc saying : semantic issue: Comparison of constant 101 with expression of type 'BOOL' (aka 'bool') is always false.
In other projects with exactly the same code, this error does not appear.
The problem is that you are holding the return values from your SQLite calls in boolean variables. You should change those variables to be int, the same type as the sqlite3_xxx() function calls return.
The clue was the error message you provided in your revised question:
Comparison of constant 101 with expression of type 'BOOL' (aka 'bool') is always false.
When you build for a 64-bit target, BOOL is defined as a bool type. And the bool type will take any non-zero value and change it to 1. For non-64 bit targets, though, BOOL is defined as signed char (which doesn't do this mapping of non-zero values to 1), which is why your code works when compiled against some targets, but not others.
Thus, when sqlite_step is returning a successful SQLITE_DONE (a non-zero value), for a 64-bit target, the BOOL is represented as bool, and the meaningful return code is getting replaced with 1 (which equates to SQLITE_ERROR). And the subsequent call to sqlite3_errmsg is getting confused, because no SQLite error has actually occurred.
If you replace those BOOL references with int (which you should do anyway), the code should work fine.
I get a result set from sqlite which looks something like this:
Null, 23, 34, 45 (being (say) 4 items returned)
The null value is correct - however when I try to add it to an array in objective-c with the following code I get an error - Null cstring - here's my code:
[array addObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)]];
I tried unsuccessfully testing for the null before adding it to the array and replacing it with a zero but so far no luck.
Could someone please help me to handle the null value so I can add maybe a zero into the array.
Thanks for any help.
As Hot Licks said, check the return value and either add a string created from that, or add [NSNull null].
const unsigned char *column0 = sqlite3_column_text(statement, 0);
if (column0)
[array addObject:[NSString stringWithUTF8String:(const char *)column0]];
else
[array addObject:[NSNull null]];
Handling null as suggested is a good option.
You can also modify queries to force column to always be TEXT using CAST(column_name AS TEXT), or force NULLs to empty strings using COALESCE(column_name, '').
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.
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?