sqlite - conditional replace and insert - android

working environment: using sqlite for android os. Doing bulk insert.
consider the following schema or sqlfiddle:
CREATE TABLE employee_data
(
id varchar(20) primary key,
name varchar(20),
dept varchar(20),
updation_date varchar(30)
);
INSERT INTO employee_data
(id, name, dept, updation_date)
VALUES
("1", "john", "tech", "2017-04-30");
INSERT INTO employee_data
(id, name, dept, updation_date)
VALUES
("2", "john2", "tech", "2017-05-01");
While adding an entry to employee_data table, following conditions must be met:
if given entry does not already exists (compared on the basis of id), then add it.
if the id already exists; then check the 'updation_date' column -
if updation_date of entry to be added is later compare to the one that already exists - then update the row.
else do nothing.
Example:
I)
need to insert ("1", "john", "management", "2017-05-01")
as there is already an entry with id = 1, check for updation_date;
now since "2017-05-01" is more recent data - update the row.
so ouput - ("1", "john", "management", "2017-05-01")
II)
need to insert ("3", "steve", "management", "2017-06-01")
as no entry with id= 3 in employee_data, directly add this one
output: ("3", "steve", "management", "2017-06-01") is added.
III)
("2", "john2", "tech", "2017-04-01");
as id=2 row exists with more recent updation_date ("2017-05-01")
do nothing.
I have tried to search but could not find solution. I can't figure out how can I use insert into select statement. Or whether I can use select from dual.
I remember using merge in oracle for similar use case.
Note:
updation_date column can be changed to timestamp format or date format for ease in comparing it.
Thanks.

SQLite is an embedded database; you are supposed to implement complex logic in your application:
void conditionalReplaceAndInsert(ContentValues cv)
{
String oldDate = DatabaseUtils.stringForQuery(db,
"SELECT updation_date FROM employee_data WHERE id = ?",
new String[]{ cv.getAsString("id") });
if (oldDate.equals(""))
db.insertOrThrow("employee_data", null, cv);
else if (oldDate.compareTo(cv.getAsString("updation_date")) < 0)
db.update("employee_data", cv, "id = ?",
new String[]{ cv.getAsString("id") });
else
; // do nothing
}

Related

Android - Change a column type in SQLite database dynamically at runtime

I have an application, where I am detecting the type of a particular column at run-time, on page load. Please refer the below code:
public String fncCheckColumnType(String strColumnName){
db = this.getWritableDatabase();
String strColumnType = "";
Cursor typeCursor = db.rawQuery("SELECT typeof (" + strColumnName +") from tblUsers, null);
typeCursor.moveToFirst();
strColumnType = typeCursor.getString(0);
return strColumnType;
}
The above method simply detects the type of column with column Name 'strColumnName'. I am getting the type of column in this case.
Now, I want to change the column type to TEXT if I am receiving INTEGER as the column type. For this, I tried the below code:
public String fncChangeColumnType(String strColumnName){
db = this.getWritableDatabase();
String newType = "";
Cursor changeCursor = db.rawQuery("ALTER TABLE tblUsers MODIFY COLUMN " + strColumnName + " TEXT", null);
if (changeCursor != null && changeCursor.moveToFirst()){
newType = changeCursor.getString(0);
}
return newType;
}
But while executing the 'fncChangeColumnType' method, I am getting this error, android.database.sqlite.SQLiteException: near "MODIFY": syntax error (code 1): , while compiling: ALTER TABLE tblUsers MODIFY COLUMN UserID TEXT
NOTE: I also replaced 'MODIFY' with 'ALTER', but still getting the same error.
Please check if this is the right method to change the type dynamically.
Please respond back if someone has a solution to this.
Thanks in advance.
In brief, the solution could be :-
Do nothing (i.e. take advantage of SQLite's flexibility)
you could utilise CAST e.g. CAST(mycolumn AS TEXT) (as used below)
Create a new table to replace the old table.
Explanations.
With SQLite there are limitations on what can be altered. In short you cannot change a column. Alter only allows you to either rename a table or to add a column. As per :-
SQL As Understood By SQLite - ALTER TABLE
However, with the exception of a column that is an alias of the rowid column
one defined with ?? INTEGER PRIMARY KEY or ?? INTEGER PRIMARY KEY AUTOINCREMENT or ?? INTEGER ... PRIMARY KEY(??) (where ?? represents a valid column name)
you can store any type of value in any type of column. e.g. consider the following (which stores an INTEGER, a REAL, a TEXT, a date that ends up being TEXT and a BLOB) :-
CREATE TABLE IF NOT EXISTS example1_table (col1 BLOB);
INSERT INTO example1_table VALUES (1),(5.678),('fred'),(date('now')),(x'ffeeddccbbaa998877665544332211');
SELECT *, typeof(col1) FROM example1_table;
The result is :-
As such is there a need to change the column type at all?
If the above is insufficient then your only option is to create a new table with the new column definitions, populate it if required from the original table, and to then replace the original table with the new table ( a) drop original and b)rename new or a) rename original, b) rename new and c) drop original)
e.g. :-
DROP TABLE IF EXISTS original;
CREATE TABLE IF NOT EXISTS original (mycolumn INTEGER);
INSERT INTO original VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(0);
-- The original table now exists and is populated
CREATE TABLE IF NOT EXISTS newtable (mycolumn TEXT);
INSERT INTO newtable SELECT CAST(mycolumn AS TEXT) FROM original;
ALTER TABLE original RENAME TO old_original;
ALTER TABLE newtable RENAME TO original;
DROP TABLE IF EXISTS old_original;
SELECT *,typeof(mycolumn) FROM original;
The result being :-
i think the sql query statement is wrong ,try
ALTER TABLE tblUsers MODIFY COLUMN id TYPE integer USING (id::integer);
instead of id use column name....
hope this helps....
EDIT:
"ALTER TABLE tblUsers MODIFY COLUMN "+strColumnName+" TYPE integer USING ("+strColumnName+"::integer);"

deleting one duplicate row in sqlite

I wish to just delete one duplicate row in here (For example, Jim 21)
SQLiteDatabase myDataBase=this.openOrCreateDatabase("Users",MODE_PRIVATE,null);
myDataBase.execSQL("CREATE TABLE IF NOT EXISTS users (name VARCHAR,age INT(3))");
myDataBase.execSQL("INSERT INTO users(name,age) VALUES ('Rob', 34)");
myDataBase.execSQL("INSERT INTO users(name,age) VALUES ('Nat', 22)");
myDataBase.execSQL("INSERT INTO users(name,age) VALUES ('Jim', 21)");
myDataBase.execSQL("DELETE FROM users WHERE name='Jim'");
Cursor c=myDataBase.rawQuery(" SELECT * FROM users", null);
int nameIndex=c.getColumnIndex("name");
int ageIndex=c.getColumnIndex("age");
c.moveToFirst();
while (c!=null){
Log.i("name",c.getString(nameIndex));
Log.i("age",Integer.toString(c.getInt(ageIndex)));
c.moveToNext();
}
I have tried this
myDataBase.execSQL("DELETE FROM users WHERE name='Jim' LIMIT 1");
But it is throwing a syntax error. I know LIMIT is not syntactically allowed in android. So how do I just delete one record of Jim when there are duplicates?
Thank you.
Limit will not work with Delete query,it's only for Select number of record
Update the query
myDataBase.execSQL("DELETE FROM users WHERE name='Jim'");
you can add more condition for remove specific records
myDataBase.execSQL("DELETE FROM users WHERE name='Jim' AND age=21 ");
There are several ways to achieve this. However, I would suggest to put a unique constraint on your name field.
myDataBase.execSQL("CREATE TABLE IF NOT EXISTS users (name text unique not null, age INT(3))");
Now for creating new entries in your users table, get a function like the following.
public void createUser(List<User> userList) {
if (userList != null && !userList.isEmpty()) {
SQLiteDatabase db = this.openOrCreateDatabase("Users",MODE_PRIVATE,null);
db.beginTransaction();
try {
for (User user : userList) {
ContentValues values = new ContentValues();
values.put("name", user.getName());
values.put("age", user.getAge());
// Replace on conflict with the unique constraint
db.insertWithOnConflict("users", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
} catch (Exception e) {
e.printStackTrace();
}
db.setTransactionSuccessful();
db.endTransaction();
}
}
In this way, you do not have to delete any duplicate rows in your table as there will be no duplicate rows either.
However, if your implementation needs duplicate rows and then deleting only the first when you are trying to delete based on some condition then you might consider using the sqlite built-in column ROWID. You get all the rows that matches your condition and save the ROWID of them all. Then you delete the row that matches the ROWID you want to delete.
delete from users where ROWID = 9
Here's the developers documentation of using ROWID.
The approach I would take is to create a table where the duplicates are automatically resolved when data is inserted. Make the "name" field a primary key. Here's the CREATE statement:
CREATE TABLE users (name TEXT PRIMARY KEY ON CONFLICT IGNORE,age INTEGER);
"ON CONFLICT IGNORE" will always keep the first "name" record in the database. If you want to always keep the last record inserted, use "ON CONFLICT REPLACE". For example:
INSERT INTO users VALUES ('Jim','21');
INSERT INTO users VALUES ('Jim','23');
INSERT INTO users VALUES ('Jim','43');
If you use "ON CONFLICT IGNORE" Then "SELECT * FROM users" would produce "Jim|21". If you use "ON CONFLICT REPLACE" Then "SELECT * FROM users" would produce "Jim|43".

SQLite set field to same value as generated id at insert

We have a requirement where some fields in a table need to have the same value as their ID. Unfortunately, we currently have to insert a new record and then, if needed, run another update to set the duplicate field (ID_2) value to equal the ID.
Here is the Android Sqlite code:
mDb.beginTransaction();
// ... setting various fields here ...
ContentValues contentValues = new ContentValues();
contentValues.put(NAME, obj.getName());
// now insert the record
long objId = mDb.insert(TABLE_NAME, null, contentValues);
obj.setId(objId);
// id2 needs to be the same as id:
obj.setId2(objId);
// but we need to persist it so we update it in a SECOND call
StringBuilder query = new StringBuilder();
query.append("update " + TABLE_NAME);
query.append(" set " + ID_2 + "=" + objId);
query.append(" where " + ID + "=" + objId);
mDb.execSQL(query.toString());
mDb.setTransactionSuccessful();
As you can see, we are making a second call to set ID_2 to the same value of ID. Is there any way to set it at INSERT time and avoid the second call to the DB?
Update:
The ID is defined as follows:
ID + " INTEGER PRIMARY KEY NOT NULL ," +
The algorithm used for autoincrementing columns is documented, so you could implement it manually in your code, and then use the new value for the INSERT.
This is quite a ugly hack, but it may be possible :
with id_table as (
select coalesce(max(seq), 0) + 1 as id_column
from sqlite_sequence
where name = 'MY_TABLE'
)
insert into MY_TABLE(ID_1, ID_2, SOME, OTHER, COLUMNS)
select id_column, id_column, 'SOME', 'OTHER', 'VALUES'
from id_table
It only works if the table ID is an AUTOINCREMENT, and is therefore managed via the documented sqlite_sequence table.
I also have no idea what happen in case of concurrent executions.
You could use an AFTER INSERT TRIGGER e.g.
Create your table (at least for this example) so that ID_2 is defined as INTEGER DEFAULT -1 (0 or any negative value would be ok)
e.g. CREATE TABLE IF NOT EXISTS triggertest (_id INTEGER PRIMARY KEY ,name TEXT ,id_2 INTEGER DEFAULT -1);
Then you could use something like (perhaps when straight after the table is created, perhaps create it just before it's to be used and drop it after done with it ) :-
CREATE TRIGGER triggertesting001
AFTER INSERT ON triggertest
BEGIN
UPDATE triggertest SET id_2 = `_id`
WHERE id_2 = -1;
END;
Drop using DROP TRIGGER IF EXISTS triggertesting001;
Example usage (testing):-
INSERT INTO triggertest (name) VALUES('Fred');
INSERT INTO triggertest (name) VALUES('Bert');
INSERT INTO triggertest (name) VALUES('Harry');
Result 1 :-
Result 2 (trigger dropped inserts run again ):-
Result 3 (created trigger) same as above.
Result 4 (ran inserts for 3rd time) catch up i.e. 6 rows updated id_2 with _id.
I'd strongly suggest reading SQL As Understood By SQLite - CREATE TRIGGER
Alternative solution
An alternative approach could be to simply use :-
Before starting transaction, retrieve mynextid from table described below
ContentValues contentValues = new ContentValues();
contentValues.put(ID,mynextid);
contentvalues.put(ID_2,mynextid++);
contentValues.put(NAME, obj.getName());
Then at end of the transactions update/store the value of mynextid in a simple single column, single row table.
i.e. you are managing the id's (not too dissimilar to how SQLite manages id's when 'AUTOINCREMENT' is specified)

Delete specific record in sqlite table based on two criteria: _id and column

I have created a sqlite table for my android app, this table has 5 columns and multiple rows, the columns being: _id, column1, column2, column3, column4.
I want to delete a specific record, for instance the record stored in column3 corresponding to _id (in a different class are the getters and setters, for this I've named the class "TableHandler")
I guess that I'm a bit confused, following is what I was planning, but for column3 I'm not sure what should be the argument, I just want to delete whatever is in that column position corresponding to _id
public void deleteValueColumn3(TableHandler value){
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, KEY_ID + " = ? AND " + KEY_COLUMN3 + " = ?",
new String[] {String.valueOf(value.getID()), ?????????);
db.close();
}
The ???????? is that I'm stuck there, maybe the whole method needs to be rewritten, I would appreciate your input.
Thanks
If you want to delete the whole record, just use the _id of the record in delete method, because that is the primary key for your table and therefore is unique. If you'd rather keep the record, you con always use the SQLiteDatabase.update method, specifying null as the new value that will replace column3 value; check out that column3 declaration has no NOT NULL tag, otherwise that could easily throw exception at you.
SQLite does not allow you to delete columns for a specific row.
You can only delete ROWS of data (delete the row that has the column _ID = 1).
Here's a quick tutorial on SQL.
How about updating that column with a null value, rather than using delete()?
ContentValues cv = new ContentValues();
cv.putNull(KEY_COLUMN3);
db.getWritableDatabase().update(
TABLE_NAME,
cv,
KEY_ID + "=?",
new String[]{String.valueOf(keyIdValue)});

How to put non Duplicate values in SQLite & Access?

My Create table Query is:--
String CREATE_LOGIN_TABLE ="CREATE TABLE IF NOT EXIST "+ TABLE_FORWARDMSG +"("+KEY_ID+ " INTEGER PRIMARY KEY AUTOINCREMENT,"+KEY_MSG_BODY+ " TEXT UNIQUE,"+KEY_MSG_ADDRESS+" TEXT UNIQUE,"+KEY_MSG_DATE+" TEXT UNIQUE" +")";
and I am access values From This Code:---
String selectQuery="SELECT * FROM "+TABLE_FORWARDMSG;
SQLiteDatabase db=this.getReadableDatabase();
Cursor cursor=db.rawQuery(selectQuery, null);
if( cursor.moveToFirst()){
for(int i=0;i<cursor.getCount();i++){
map.put("msgBody", cursor.getString(1));
map.put("msgAddress", cursor.getString(2));
map.put("msgDate", cursor.getString(3));
user.add(map);
cursor.moveToNext();
}
}
cursor.close();
db.close();
Now My Problem is that:----
If I remove UNIQUE from the CREATE TABLE Then I get all duplicate values means if I insert same value it create new row, and if I am using UNIQUE in CREATE TABLE, then every time cur.getCount() value is 1. I am new in SQLite. Please tell me whats the problem.
Your code looks like Ok. Maybe you shouldn't make KEY_MSG_DATE like UNIQUE. Try to get more info about what you want to do.
What you have implemented is that in every entry in every column has to be unique within the column but I assume what you want is that the row containing the columns msgBody, msgAddress and msgDate has to be unique within the table.
You can achieve that by placing all columns together in the UNIQUE clause:
UNIQUE(msgBody, msgAddress, msgDate) ON CONFLICT REPLACE

Categories

Resources