I'm developing for iOS and I have a previous database really poorly constructed. Now there is already 140 sealed but in my update I make a new database but I can not delete the data... How can I change the data from database A to database B ? I use sqlite3 in xCode 5
Here is an example code altering a column in a database table:
- (void)alterDB {
sqlite3_stmt *statement;
sqlite3_stmt *selectStmt;
const char *columnExists = "select callCount from lastCall";
//Alter the table ONLY IF column we want doesn't exist
if (sqlite3_prepare_v2(database, columnExists, -1, &selectStmt, NULL) != SQLITE_OK)
{
NSString *updateSQL = [NSString stringWithFormat:#"ALTER TABLE lastCall ADD COLUMN callCount INTEGER"];
const char *update_stmt = [updateSQL UTF8String];
sqlite3_prepare_v2(database, update_stmt, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE) {
NSLog(#"DATABASE ALTERED");
} else {
NSLog(#"error altering database");
}
}
}
I hope this gives you an idea.
Write migration code,
that will select the data from Database A and will insert it into Database B.
When dealing with different schemas, I have a method that I always call when opening the database that checks the schema version in a table set aside for that purpose and then doing any alterations as needed to the schema.
- (void) checkSchema {
if([[self preferenceForKey:#"SchemaVersion"] isEqualToString: #"1"]) {
[db sqlExecute:#"create table Documents(documentID int, description text, type text, url text, modifiedDate text, read int, fileName text, needsUpdate int, primary key(documentID,type));"];
[self setPreference:#"2" ForKey:#"SchemaVersion"];
}
// etc.
}
So while developing, I can add schema changes and no matter what version of the app someone is using, when they upgrade they will get the latest schema changes.
It is because of this, and the work of changing all my accessor methods to the database that I am now using SimpleDB (https://github.com/AaronBratcher/SimpleDB), a key/value db I wrote. Completely different way of accessing data that makes things a lot simpler.
Related
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
I'm trying to figure out the fastest method of adding a (different) guid to a new field, where there could be thousands of records.
I'm pretty sure SQLite doesn't provide a function which will create a guid. So I'm going need to somehow have to create this for each row.
Ideas I've come up with do far.
Simply executing an update query where the field is blank and limit 1. Obviously this is going to be slow even within a transaction.
Use cte (common table extension) with a temp table where I build up the values including the guid, the update / join. However I can't seem to get temp tables to work in iOS, I keep getting a library called out of sequence error.
Using inline sub queries with the update query. However I don't know how to identify the rows which require the guid, other than them being blank. I'd need to use recursion, which again would require cte, I believe?
EDIT: Here's my solution, credit to #CL. for the ideas...
void newguid(sqlite3_context *context, int argc, sqlite3_value **argv)
{
if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
sqlite3_result_null(context);
return;
}
#autoreleasepool {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid);
CFRelease(uuid);
sqlite3_result_text(context, [uuidString UTF8String], -1, SQLITE_TRANSIENT);
}
}
- (void)createSqlGuidFunction:(sqlite3*)db
{
if (sqlite3_create_function_v2(db, "newguid", 1, SQLITE_ANY, NULL, &newguid, NULL, NULL, NULL) != SQLITE_OK)
{
NSLog(#"%s: sqlite3_create_function_v2 error: %s", __FUNCTION__, sqlite3_errmsg(db));
}
}
The documentation says that
applications can generate globally unique identifiers using this function together with hex() and/or lower() like this:
hex(randomblob(16))
lower(hex(randomblob(16)))
But if you want to use a different mechanism to generate the GUID, you can register your own function.
I would recommend doing something like this
ALTER TABLE newTable RENAME TO oldTable;
CREATE TABLE newTable (//Define your columns here along with the GUID column);
Then you can select all the values from oldTable and insert into newTable along with the GUID. Once you're done, use
DROP TABLE oldTable
In My current ios application I have to add tables and insert data to the added table for new version.
By taking Upgrade into consideration I did it through the code itself Like below
-(void)lessThan100TableQuery
{
NSString *query=[NSString stringWithFormat:#"CREATE TABLE 'Table_Name' ('item_id' INTEGER PRIMARY KEY NOT NULL , 'item_section' INTEGER, 'item_no' INTEGER, 'bullet_no' VARCHAR, 'heading' INTEGER, 'hide_controls' INTEGER, 'description' VARCHAR);INSERT INTO 'Table_Name' VALUES(1,1,0,'',1,1,'Condition/adequacy of distributor''s/supply intake equipment');"
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
if(sqlite3_step(statement) != SQLITE_DONE) {
return NO;
}
sqlite3_finalize(statement);
}
}
(like above I add 100 rows I skipped them here)
But by calling the above method only the table get added without the values
I cross checked it by running the above query in sqlitemanager and working fine without any issue. It is not working in code.
Help me out folks
The sqlite3_prepare_v2() documentation says:
These routines only compile the first statement in zSql
To execute multiple statements, you have to use a loop, and use pzTail to skip over the previously-executed statement.
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'.
Im super new to ios (Objective-C) and sql and Im following a tutorial of sqlite (specifically sqlite3) found here: http://www.tutorialspoint.com/ios/ios_sqlite_database.htm
and i implemented everything correctly. The problem is that if I try to enter information with the same reg id (The primary key which is used to find elements in the database), it complains about the key not being unique.
- (BOOL) saveData:(NSString*)registerNumber name:(NSString*)name
department:(NSString*)department year:(NSString*)year;
{
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
NSString *insertSQL =
[NSString stringWithFormat:#"insert into studentsDetail (regno,name, department, year) values (\"%d\",\"%#\", \"%#\", \"%#\")",[registerNumber integerValue], name, department, year];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(database, insert_stmt,-1, &statement, NULL);
int s = sqlite3_step(statement);
const char *errmsg = sqlite3_errmsg(database);
if (s == SQLITE_DONE)
{
sqlite3_reset(statement);
return YES;
}
else {
sqlite3_reset(statement);
return NO;
}
}
return NO;
}
How do I go about making the saveData: action actually update the entry inside the database instead of just being rejected?
Thanks!
"I want to extend it so that if I try to enter an existing reg id, it
will overwrite the remaining properties with the new ones"
If this is what you are trying to accomplish then you should not be using an INSERT statement. The purpose of the primary key is to protect the database from duplicate rows with the same primary key. When you look at it that way, the rejection is appropriate.
What you really want is to use INSERT OR REPLACE
If you're a beginner to both sql and objective c, I'd recommend a sqlite wrapper I created that is aimed at beginners: TankDB
Edit:
Code example:
INSERT OR REPLACE INTO studentsDetail (regno,name, department, year) values (\"%d\",\"%#\", \"%#\", \"%#\")"
Refer to this question to make sure that you don't actually mean to use 'UPSERT'. Beware that INSERT OR REPLACE does not update the existing row with the new information. It will completely delete the existing row and replace it.
Edit2: Open, prepare, step, finalize, close
const char *dbpath = [_databasePath UTF8String];
if(sqlite3_open(dbpath, &_database) == SQLITE_OK) {
const char *command = [query UTF8String];
// Perform the query
if (sqlite3_prepare_v2(_database, command, -1, &selectstmt, NULL) == SQLITE_OK) {
// For each row returned
while (sqlite3_step(selectstmt) == SQLITE_ROW) {
// Do stuff
}
}
sqlite3_finalize(selectstmt);
}
sqlite3_close (_database);