Is this Sqlite3 statement OK? - ios

I'm a noob when it comes to sqlite and not quite sure how to do this.
I want a database with a bunch of row, containing one word each. When the user types a word, I will validate it by checking if its in the database.
Things I dont have, I guess, and don't know how to create, is an index? How do I insert that? How do I write the query to take advantage of index?
I also have two columns in there, "id and word". Is it good to have the id or does it just take up space?
This is what I got so far:
CREATE TABLE words (id INTEGER PRIMARY KEY, word VARCHAR(15));
I don't want words longer then 15 characters, so I set the VARCHAR(15);
INSERT INTO words(word) VALUES('hello');
INSERT INTO words(word) VALUES('bye');
etc. for all words
And to check a word:
NSString *sql = [NSString stringWithFormat:#"SELECT EXISTS(SELECT 1 FROM words WHERE word=\"%#\" LIMIT 1)", word];
const char *sqlStatement = [sql UTF8String];
if (sqlite3_prepare_v2(db, sqlStatement, -1, &selectStmt, NULL) == SQLITE_OK)
{
int count = 0;
while(sqlite3_step(selectStmt) == SQLITE_ROW)
{
count = sqlite3_column_int(selectStmt, 0);
}
NSLog(#"COUNT: %i",count);
//If count is 1, we have a match.
}

Yes. Your Statement is ok.
You can also use ' intead of ":
NSString *sql = [NSString stringWithFormat:#"SELECT EXISTS(SELECT 1 FROM words WHERE word='%#' LIMIT 1)", word];
Is it good to have the id or does it just take up space?
It depends on your need, I will suggest you should keep an Id field as primary key.
For creating index you can use:
CREATE INDEX yourIndexName ON yourTableName ( yourColumnName )
For more about indexing check sqlite

Related

After Delete Row How To Reset Sqlite id value?

i have created sqlite database as follow
NSString * sqlStmt =#"CREATE TABLE IF NOT EXISTS SONGS (ID INTEGER PRIMARY KEY AUTOINCREMENT, MOVIENAME TEXT, SONGNAME TEXT)";
after deleting row 7 in database the id values are 1,2,3,4,5,6,8,9... by using following code where idNumber =7.
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &_SQliteDB) == SQLITE_OK)
{
NSString *sql = [NSString stringWithFormat:#"delete from SONGS where ID=%d",idNumber];
const char *del_stmt = [sql UTF8String];
char *error ;
if (sqlite3_exec(_SQliteDB,del_stmt, NULL, NULL, &error) == SQLITE_OK)
{
NSLog(#"sucessfully delete");
} else
{
NSLog(#"unable to delete");
}
sqlite3_close(_SQliteDB);
}
else
{
NSLog(#"unable to open");
}
i need to rearrange that order as sequentially as 1,2,3,4,5,6,7,8?
Any help would be appreciated.
You shouldn't re-order IDs, because other tables may have reference for this ID. Because if any how you are able to do that then next problem will arise in front of you.
For example:- You have rows 1, 2, 3 and you delete 2, then you have 1, 3. And you sorted this any how. Now Issue is when you going to add new item it starts with 4 not 3.
In your case, I recommend using the Row_Number if it's for a display reason.
There's no problem having gaps in a database.
If you want the order as sequentially, ORDER BY is what you are looking for.
After deleting, retrieve records using select query as shown below :
NSString *select = #"SELECT * FROM SONGS ORDER BY ID"
For more detail about ORDER BY , refer to link1 and for how to use it in iOS, refer to link2
You must define all data from database to new array or list.After that you must delete table and rewrite all data from array or list to database.
You can look for android studio solution ;
https://stackoverflow.com/a/57862686/8363647

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

How to check and then insert in sqlite?

I have a area table in sqlite database. Everytime i am just performing insert operation onto the sqlite database. How can i check if any record exists or not. If not exist simply insert. If exist then update records.
Please help me.
you can do easily "insert or ignore into tbl_name"
here you can see the example
http://www.raywenderlich.com/913/sqlite-tutorial-for-ios-making-our-app
this would be usefull for you....
http://www.sqlite.org/lang_conflict.html
Yes, you can do that with a single query.
INSERT ON CONFLICT IGNORE should help you: http://www.sqlite.org/lang_conflict.html
Put a unique key on the name, this will create a conflict when you try inserting a record if the name already exists.
The default is ABORT, so without the IGNORE, the statement will return an error. If you don't want that, use IGNORE.
You can do INSERT OR REPLACE if you have a primary key on the table. For example:
sqlite3 *database = NULL;
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:#"test.sqlite"];
int rc = sqlite3_open([path UTF8String], &database);
NSAssert(rc == SQLITE_OK, #"Open failed");
// note, use PRIMARY KEY when creating table
rc = sqlite3_exec(database, "CREATE TABLE IF NOT EXISTS test (animal TEXT PRIMARY KEY, sound TEXT)", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"Create failed: %s", sqlite3_errmsg(database));
// create a record that will be replaced by the subsequent `INSERT OR REPLACE`
rc = sqlite3_exec(database, "INSERT INTO test (animal, sound) VALUES ('dog', 'meow')", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"INSERT failed: %s", sqlite3_errmsg(database));
// this will REPLACE entry if value with same PK found, otherwise it would INSERT
rc = sqlite3_exec(database, "INSERT OR REPLACE INTO test (animal, sound) VALUES ('dog', 'woof')", NULL, NULL, NULL);
NSAssert(rc == SQLITE_OK, #"INSERT failed: %s", sqlite3_errmsg(database));
// now retrieve values and make sure it worked like we thought it would
sqlite3_stmt *statement = NULL;
rc = sqlite3_prepare_v2(database, "SELECT animal, sound FROM test", -1, &statement, NULL);
NSAssert(rc == SQLITE_OK, #"prepare SELECT failed: %s", sqlite3_errmsg(database));
while ((rc = sqlite3_step(statement)) == SQLITE_ROW) {
const unsigned char *animal = sqlite3_column_text(statement, 0);
const unsigned char *sound = sqlite3_column_text(statement, 1);
NSLog(#"%s goes %s", animal, sound);
}
NSAssert(rc == SQLITE_DONE, #"step failed: %s", sqlite3_errmsg(database));
sqlite3_finalize(statement);
sqlite3_close(database);
And that will report that the INSERT OR REPLACE replaced the previous value rather than inserting second record:
2013-11-21 08:59:25.285 AnimalSounds[53549:70b] dog goes woof
If you don't have primary key, rather than this simple INSERT OR REPLACE, you'd have to break it into two steps, either:
Look for record with SELECT: If found, do UPDATE; if not found, do INSERT.
First DELETE any records that would match whatever criteria you want, and then do INSERT.
This first approach is a bit safer, but you could use the second approach if you had to (though you would probably use transactions a do a ROLLBACK if you had any problems). Needless to say, the INSERT OR REPLACE approach is even easier, but requires a primary key.
First call get record query in Database. Here I am add a example, I am checking that user login information available in database or not. So add below code. IF User record is available than i get record array otherwise nil.
+(NSArray*)getTBL_LOGIN
{
NSMutableArray *Favourite=[[NSMutableArray alloc]init];
sqlite3 *database;
TabBarAppDelegate *x=(TabBarAppDelegate*)[[UIApplication sharedApplication]delegate];
if(sqlite3_open([[x dataBasePath] UTF8String],&database) == SQLITE_OK) {
NSString *str = [NSString stringWithFormat:#"select * from tbl_login"];
const char *sqlStmt=[str UTF8String];
sqlite3_stmt *compiledStmt;
if(sqlite3_prepare_v2(database, sqlStmt, -1, &compiledStmt, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStmt)==SQLITE_ROW)
{
NSString *uid=[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStmt, 0)];
NSString *username=[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStmt, 1)];
NSDictionary *d=[NSDictionary dictionaryWithObjectsAndKeys:uid,#"uid",username,#"username",nil];
[Favourite addObject:d];
}
}
sqlite3_finalize(compiledStmt);
}
sqlite3_close(database);
if([Favourite count]>0)
{
NSArray *ar=[NSArray arrayWithArray:Favourite];
return ar;
} else {
return nil;
}
}
If you get the record count >=1 then record exist so you have to call update query if you get record count 0 than record is not available in database so you have to call insert query
In a situation where I imported all updates into another database table, I could use following:
-- Existing table: t(uc UNIQUE, v1, v2, v3);
-- Updates table: ut(uc UNIQUE, v2);
INSERT OR REPLACE INTO t
SELECT ut.uc, et.v1, ut.v2, et.v3 FROM ut
LEFT JOIN t AS et ON ut.uc=et.uc;
This statement will insert new rows from ut into t. Existing rows are replaced with a row containing new data from ut and existing data from t.
For this to work, you must have a UNIQUE column (which makes sense as you are looking for a row update or insert a new one), and have new data available so it can be queried (in same or another database).
This worked for me, hope it may help you.
Another solution, maybe with better performance is using two statements:
UPDATE t SET v1='some value', v2=123 WHERE unique_col='some_id';
INSERT OR IGNORE t(v1, v2, unique_col) VALUES('some value', 123, 'some_id');
UPDATE will become a null operation when 'some_id' is not found.
INSERT will ignore all existent 'some_id'.

iOS sqlite3_step hold / freeze after last row data

My sqlite3_step holds for a 1s after read of last row data. Why?
-(NSDictionary*)specificationItemsForConfigurationsIds:(NSString*)configurationsIdsStr
{
[self databaseOpen];
NSString *query = [NSString stringWithFormat:#"SELECT SpecItem.id,SpecItem.name,ConfigurationSpec.configuration_id\
FROM (SpecItem INNER JOIN ConfigurationSpec ON ConfigurationSpec.spec_item_id=SpecItem.id)\
WHERE (SpecItem.parent_id=12 OR SpecItem.parent_id=34 OR SpecItem.id=23 OR SpecItem.id=27) AND ConfigurationSpec.configuration_id IN (%#)",configurationsIdsStr];
sqlite3_stmt *statement;
NSMutableDictionary* configurationsWithSpecItems = [NSMutableDictionary new];
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
int specItemId = sqlite3_column_int(statement, 0);
NSString* specItemName = [self sqlite3_column_text_asString_ofStatement:statement
atColumn:1];
int configId = sqlite3_column_int(statement, 2);
NSString* configIdNumber = [NSString stringWithFormat:#"%d",configId];
NSMutableArray* specItems = [configurationsWithSpecItems objectForKey:configIdNumber];
if(specItems == nil)
{
specItems = [NSMutableArray new];
[configurationsWithSpecItems setObject:specItems
forKey:configIdNumber];
}
SpecificationItem* specItem = [SpecificationItem specificationItemWithId:specItemId
name:specItemName];
[specItems addObject:specItem];
// When we read last row data, getting from here to POINT 2 takes 1s
}
// POINT 2
sqlite3_finalize(statement);
}
[self databaseClose];
return configurationsWithSpecItems;
}
Single read of one row takes 2-3ms, but after last one getting out of while loop takes 1s, which is too much for me.
EXPLAIN QUERY PLAN output for this query:
0 0 1 SCAN TABLE Configuration (~100000 rows)
0 0 0 EXECUTE LIST SUBQUERY 1
1 0 0 SEARCH TABLE Configuration USING AUTOMATIC COVERING INDEX (model_id=?) (~7 rows)
0 1 0 SEARCH TABLE SpecItem USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
There are two explanations for the delay; one or both might apply:
All the records that match are at the beginning of the Configuration table. After the last matching record, SQLite still has to search through all the remaining records, but none matches.
SQLite creates a temporary index on the model_id column because it estimates that the query would be even slower without it. After the query has finished, that index must be deleted again; what you see is the time needed to synchronize at the end of the (automatic) transaction.
Create an index on the model_id column will help avoiding both of these points.
If possible, you should try to merge the subquery (in configurationsIdStr) into the outer query; instead of:
... ConfigurationSpec.configuration_id IN (
SELECT configuration_id FROM Configuration WHERE model_id = 42)
use something like this:
... ConfigurationSpec.model_id = 42
Avoiding that indirection makes it much easier for SQLite to optimize the query execution.

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?

Resources