66

as can be seen in the documentation the syntax to make insert or update is : INSERT OR REPLACE INTO <table> (<columns>) VALUES (<values>), my question is there any function that merge the following ?

public long insert (String table, String nullColumnHack, ContentValues values) 
public int update (String table, ContentValues values, String whereClause, String[] whereArgs)

or it has to be done with a prepared SQL statement and rawQuery?

What's the best practices to do an insert or update in Android?

Luis Neves
  • 1,067
  • 2
  • 10
  • 21
  • 3
    You could call update on a particular row.. Ex. update ___ where rowId = 4.. and if update returns 0 .. you know you don't have that row, so just insert it. – dymmeh Nov 09 '12 at 16:15

9 Answers9

97

I believe that you are asking how to INSERT new rows or UPDATE your existing rows in one step. While that is possible in a single raw SQL as discussed in this answer, I found that it easier to do this in two steps in Android using SQLiteDatabase.insertWithOnConflict() using CONFLICT_IGNORE for conflictAlgorithm.

ContentValues initialValues = new ContentValues();
initialValues.put("_id", 1); // the execution is different if _id is 2
initialValues.put("columnA", "valueNEW");

int id = (int) yourdb.insertWithOnConflict("your_table", null, initialValues, SQLiteDatabase.CONFLICT_IGNORE);
if (id == -1) {
    yourdb.update("your_table", initialValues, "_id=?", new String[] {"1"});  // number 1 is the _id here, update to variable for your code
}

This example assumes that the table key is set for column "_id", that you know the record _id, and that there is already row #1 (_id=1, columnA = "valueA", columnB = "valueB"). Here is the difference using insertWithOnConflict with CONFLICT_REPLACE and CONFLICT_IGNORE

  • CONFLICT_REPLACE will overwrite existing values in other columns to null (ie. columnB will become NULL and the result will be _id=1, columnA = "valueNEW", columnB = NULL). You lose existing data as result and I do not use it in my code.
  • CONFLICT_IGNORE will skip the SQL INSERT for your existing row #1 and you will SQL UPDATE this row in the next step preserving the content of all other columns (ie. the result will be _id=1, columnA = "valueNEW", columnB = "valueB").

When you attempt to insert new row #2 which does not exist yet, the code will only execute the SQL INSERT in the first statement insertWithOnConflict (ie. the result will be _id=2, columnA = "valueNEW", columnB = NULL).

Beware of this bug which is causing SQLiteDatabase.CONFLICT_IGNORE to malfunction on API10 (and probably API11). The query is returning 0 instead of -1 when I test on Android 2.2.

If you do not know the record key _id or you have a condition that will not create a conflict, you can reverse the logic to UPDATE or INSERT. This will keep your record key _id during UPDATE or create a new record _id during INSERT.

int u = yourdb.update("yourtable", values, "anotherID=?", new String[]{"x"});
if (u == 0) {
    yourdb.insertWithOnConflict("yourtable", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}

The above example assumes that your just want to UPDATE timestamp value in the record for example. If you call insertWithOnConflict first, INSERT will create new record _id due to the difference in the timestamp condition.

theczechsensation
  • 4,215
  • 2
  • 24
  • 25
  • It would be simpler to use CONFLICT_REPLACE instead of CONFLICT_IGNORE, as others mentioned. – aleb Nov 13 '14 at 18:27
  • 4
    @aleb - There is a huge difference between CONFLICT_IGNORE and CONFLICT_REPLACE when the query does not specify all the columns in the table. When you use CONFLICT_REPLACE, the query will put NULL values into all columns which do not have any value specified in the ContentValues variable. In my case, I needed to only update one column out of five without knowing the value of the remaining four. Using CONFLICT_REPLACE forced the other four columns to erase values. – theczechsensation Nov 16 '14 at 01:57
  • ok, good to know, but the question was about "INSERT OR REPLACE INTO" – aleb Nov 16 '14 at 09:27
  • 1
    Yes, and the answer is exactly dealing with Inserting or Replacing data. It overcomes the limitations of CONFLICT_REPLACE, which resets values to NULL for columns excluded in the initialValues param. The answer which you provided destroys data, so please consider up-voting my answer rather than down-voting it. Thank you. – theczechsensation Nov 18 '14 at 04:29
  • The question is about "INSERT OR REPLACE", see https://www.sqlite.org/lang_conflict.html to see what it does. It "deletes pre-existing rows that are causing the constraint violation", not only some columns. I still think your answer is not for this question. – aleb Nov 18 '14 at 08:59
  • Thank you for giving me a "lecture" on SQL. Trust me, I have done my reading and my users would not be very happy to see their data deleted if I used your answer.... – theczechsensation Nov 20 '14 at 04:44
  • I'm not sure what you want from me. File a bug with SQLite complaining that some customers might see their data deleted if unaware programmers use replace()..? – aleb Nov 20 '14 at 14:20
  • 4
    Beware that CONFLICT_REPLACE will delete the existing row and insert the new values. So if you want to update just one column with new value and do no pass in values for other columns they will end up as null – Ganesh Krishnan Apr 22 '15 at 13:42
  • I don't know how does this work the [insertWithOnConflict() documentation](https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#insertWithOnConflict(java.lang.String,%20java.lang.String,%20android.content.ContentValues,%20int)) says it returns -1 if either the input parameter conflictAlgorithm = CONFLICT_IGNORE or an error occurred so I thought with CONFLICT_IGNORE -1 is always returned – Mohammad Yahia Apr 06 '17 at 11:14
  • it seems that the documentatin changes with time [this version](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/database/sqlite/SQLiteDatabase.java#SQLiteDatabase.insertWithOnConflict%28java.lang.String%2Cjava.lang.String%2Candroid.content.ContentValues%2Cint%29) says the row ID of the newly inserted row OR the primary key of the existing row if the input param 'conflictAlgorithm' = CONFLICT_IGNORE OR -1 if any error – Mohammad Yahia Apr 06 '17 at 11:52
  • just `yourdb.insert` also returns result, so what is the difference between `insert` and `insertWithOnConflict`? – user25 Dec 31 '18 at 17:01
  • and why do you cast long to int? you should just use long instead of int... – user25 Dec 31 '18 at 17:04
44

this is your method SQLiteDatabase.insertWithOnConflict(). to understand what it does refer to this document on sqlite

kdehairy
  • 2,630
  • 22
  • 27
  • 19
    This method should be called with the `CONFLICT_REPLACE` flag to work as the question says. – Mister Smith Nov 15 '12 at 10:46
  • Shouldn't it be the updateWithOnConflict, so that you can define a where clause ? – NikkyD Jan 10 '14 at 14:49
  • 4
    @NikkyD `updateWithOnConflict` won't insert a new row if it does not exist. Where as `insertWithOnConflict` works like this: If the row exists (conflict) update it, if not (no conflict) insert a new one. Which I think is what the question is about. – kdehairy Jan 11 '14 at 12:59
  • 2
    But what index is used to determine if it exists ? i dont see a "where" clause there – NikkyD Jan 13 '14 at 00:16
  • 2
    A conflict is raised if some PRIMARY KEY or UNIQUE KEY constraint will be broken by the insert. If a conflict is raised, the row with the UNIQUE KEY or the PRIMARY KEY that caused the conflict will be updated with the new data. – kdehairy Jan 14 '14 at 10:57
  • 7
    `insertWithOnConflict` is not correct to use with `CONFLICT_REPLACE` flag when you have `FOREIGN KEY` constraint with `ON DELETE CASCADE` because all records in other tables that references primary key in that table will be deleted when conflict happens. – mixel Apr 25 '15 at 20:54
10

SQLiteDatabase.replace() does this, it basically calls:

insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE);

Too bad the documentation is not very clear.

aleb
  • 2,490
  • 1
  • 28
  • 46
6

The operation name for that is "upsert" and how I solve it is identifying the columns of your table that make a row UNIQUE.

Example: _id, name, job, hours_worked

The columns which we'll use are name and job.

private int getID(String name, String job){
    Cursor c = dbr.query(TABLE_NAME,new String[]{"_id"} "name =? AND job=?",new String[]{name,job},null,null,null,null);
    if (c.moveToFirst()) //if the row exist then return the id
        return c.getInt(c.getColumnIndex("_id"));
    return -1;
}

In your database manager class:

public void upsert(String name, String job){
    ContentValues values = new ContentValues();
    values.put("NAME",name);
    values.put("JOB",job);

    int id = getID(name,job);
    if(id==-1)
        db.insert(TABLE_NAME, null, values);
    else
        db.update(TABLE_NAME, values, "_id=?", new String[]{Integer.toString(id)});
}
z1lV3r
  • 699
  • 7
  • 12
3

SQLiteDatabase.replace() is probably what you are looking for. I haven't tried it but the doc says it returns the row ID of the newly inserted row, so it may work.

Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
2

I had the same issue, but I realized when my object already has an Id it should be updated and when it does not have an Id it should be inserted so this is step by step what I did to resolve the issue:

1- in your object getId use Integer or initialize the Id how you see fit: here is my code

public Integer getId() {
    return id;
}

2- check the Id in your method for insert or update after you put everything in ContentValues:

if(myObject.getId()!= null ){
        int count = db.update(TABLE_NAME,myContentValues,ID + " = ? ",
                new String[]{String.valueOf(myObject.getId())});
        if(count<=0){
            //inserting content values to db
            db.insert(TABLE_NAME, null, myContentValues);
        }
    } else {
        db.insert(TABLE_NAME, null, myContentValues);
    }

what happens here is that I check for Id if does exist I update that row but if update method returns -1 it means there were no rows with that Id so I insert the row, and if it does not have an Id I insert it.

hope this helps.

Pouya Danesh
  • 1,557
  • 2
  • 19
  • 36
1

What about replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues)

Docs say... Convenience method for replacing a row in the database. Inserts a new row if a row does not already exist.

Basically it calls insertWithOnConflict

Alezis
  • 1,182
  • 3
  • 13
  • 25
0

for me, none of the approaches are work if you don't have "_id" (Primary Key)

you should first call update, if the affected rows are zero, then insert it with ignore:

 String selection = MessageDetailTable.SMS_ID+" =?";
 String[] selectionArgs =  new String[] { String.valueOf(md.getSmsId())};

 int affectedRows = db.update(MessageDetailTable.TABLE_NAME, values, selection,selectionArgs);

 if(affectedRows<=0) {
     long id = db.insertWithOnConflict(MessageDetailTable.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
 }
Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
0

You can do like this.

        sqliteDatabase?.let {
        // 1. use sql to do this
        // val sql = "insert or replace into $table (key, value) values ('$key', '${obj.toString()}')"
        
        // 2. use update and ContentValues
        // it.execSQL(sql)
        // it.replace(table, "", values)

        // 3. try to update if affected row count is 0, do insert
        val updateRow = it.update(table, values, "$column_key = ?", arrayOf(key))
        Log.d(TAG, "update row: $updateRow")
        if (updateRow == 0) {
            val rowId = it.insertWithOnConflict(table, "", values, SQLiteDatabase.CONFLICT_IGNORE)
            Log.d(TAG, "insert row: $rowId")
        }
    }