65

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?

4

9 回答 9

94

我相信您在问如何一步插入新行或更新现有行。虽然这在这个答案中讨论的单个原始 SQL 中是可能的,但我发现在 Android 中使用SQLiteDatabase.insertWithOnConflict()使用 CONFLICT_IGNORE 进行冲突算法更容易做到这一点。

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
}

此示例假定为列“_id”设置了表键,您知道记录 _id,并且已经存在第 1 行(_id=1,columnA = “valueA”,columnB = “valueB”)。这是使用 insertWithOnConflict 与 CONFLICT_REPLACE 和 CONFLICT_IGNORE 的区别

  • CONFLICT_REPLACE 会将其他列中的现有值覆盖为 null(即 columnB 将变为 NULL,结果将是 _id=1,columnA = "valueNEW",columnB = NULL)。结果你丢失了现有数据,我没有在我的代码中使用它。
  • CONFLICT_IGNORE 将跳过现有行#1 的 SQL INSERT,您将在下一步中 SQL UPDATE 此行,保留所有其他列的内容(即结果将是 _id=1,columnA = "valueNEW",columnB = "值 B")。

当您尝试插入尚不存在的新行 #2 时,代码将仅在第一条语句 insertWithOnConflict 中执行 SQL INSERT(即结果将是 _id=2,columnA = "valueNEW",columnB = NULL)。

请注意这个导致 SQLiteDatabase.CONFLICT_IGNORE 在 API10(可能还有 API11)上发生故障的错误。当我在 Android 2.2 上测试时,查询返回 0 而不是 -1。

如果您不知道记录键 _id 或者您有一个不会产生冲突的条件,您可以将逻辑反转为 UPDATE 或 INSERT。这将在 UPDATE 期间保留您的记录密钥 _id 或在 INSERT 期间创建新记录 _id。

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

上面的示例假设您只想更新记录中的时间戳值。如果先调用insertWithOnConflict,由于时间戳条件的不同,INSERT会创建新的记录_id。

于 2013-12-13T13:52:09.337 回答
44

这是您的方法SQLiteDatabase.insertWithOnConflict()。了解它在 sqlite上引用本文档的内容

于 2012-11-12T10:36:14.063 回答
10

SQLiteDatabase.replace()这样做,它基本上调用:

insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE);

太糟糕了,文档不是很清楚。

于 2014-11-16T11:25:48.530 回答
6

它的操作名称是“upsert”,我如何解决它是识别表中使行唯一的列。

示例:_id、姓名、工作、hours_worked

我们将使用的列是名称和工作。

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;
}

在您的数据库管理器类中:

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)});
}
于 2014-07-31T16:52:58.740 回答
3

SQLiteDatabase.replace()可能是您正在寻找的。我没有尝试过,但文档说它返回新插入的行的行 ID,所以它可能工作。

于 2012-11-09T16:32:43.660 回答
2

我遇到了同样的问题,但我意识到当我的对象已经有一个 Id 时应该更新它,而当它没有 Id 时应该插入它,所以这是我为解决问题所做的一步一步:

1- 在您的对象 getId 中使用 Integer 或初始化您认为合适的 Id:这是我的代码

public Integer getId() {
    return id;
}

2-在将所有内容放入 ContentValues 后,检查方法中的 ID 以进行插入或更新:

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);
    }

这里发生的是我检查 Id 如果确实存在我更新该行但是如果更新方法返回 -1 这意味着没有具有该 Id 的行所以我插入该行,如果它没有 Id 我插入它。

希望这可以帮助。

于 2016-07-18T20:08:17.600 回答
1

replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues)怎么样

文档说... 替换数据库中的一行的便捷方法。如果行不存在,则插入新行。

基本上它调用insertWithOnConflict

于 2018-01-22T15:37:44.060 回答
0

对我来说,如果你没有“_id”(主键),这些方法都不起作用

您应该首先调用更新,如果受影响的行为零,然后将其插入忽略:

 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);
 }
于 2020-05-25T23:02:57.543 回答
0

你可以这样做。

        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")
        }
    }
于 2021-08-13T13:23:42.263 回答