安卓SQLite插入或更新

66

从文档中可以看到插入或更新的语法是:INSERT OR REPLACE INTO <table> (<columns>) VALUES (<values>),我的问题是是否有函数可以合并以下内容?

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

是使用准备好的SQL语句和rawQuery执行,还是必须这样做?

在Android中执行插入或更新的最佳实践是什么?


3
您可以对特定行调用更新操作。例如,update ___ where rowId = 4,如果更新返回0,则说明您没有该行,因此只需插入它。 - dymmeh
9个回答

97

我认为你想知道如何在一步中插入新行或更新现有行。虽然可以在单个原始 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,并且已经有了第一行(_id = 1,columnA = "valueA",columnB = "valueB")。这里是使用 insertWithOnConflict 和 CONFLICT_REPLACE 以及 CONFLICT_IGNORE 的不同之处:

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

当您尝试插入尚不存在的新行 #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,则由于时间戳条件的差异,插入操作将创建新的记录_id。


4
在查询未指定表中所有列时,CONFLICT_IGNORE和CONFLICT_REPLACE之间存在巨大的差异。当您使用CONFLICT_REPLACE时,查询将在ContentValues变量中未指定值的所有列中放入NULL值。在我的情况下,我只需要更新五个列中的一个,而不知道其他四个的值。使用CONFLICT_REPLACE会强制删除其他四列的值。 - theczechsensation
1
是的,答案确切地涉及插入或替换数据。它克服了CONFLICT_REPLACE的限制,该限制会将initialValues参数中排除的列的值重置为NULL。你提供的答案会破坏数据,所以请考虑给我的答案点赞而不是踩它。谢谢。 - theczechsensation
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - theczechsensation
我不确定你想从我这里得到什么。是要在SQLite中提交一个错误报告,抱怨一些客户可能会看到他们的数据被删除,如果不知情的程序员使用replace()函数吗? - aleb
4
请注意,CONFLICT_REPLACE将删除现有的行并插入新值。因此,如果您想仅使用新值更新一个列,并且不传递其他列的值,则它们将变为空值。 - Ganesh Krishnan
显示剩余7条评论

44

19
为了使得该方法按照问题所述的方式工作,应使用“CONFLICT_REPLACE”标志来调用。 - Mister Smith
难道不应该使用updateWithOnConflict,这样你就可以定义一个where子句了吗? - NikkyD
4
updateWithOnConflict方法如果不存在该行,则不会插入新行。而insertWithOnConflict方法的工作方式如下:如果该行存在(冲突),则对其进行更新,否则(无冲突)插入新行。我认为这就是问题所在。 - kdehairy
2
但是用于确定它是否存在的索引是什么?我在那里没有看到“where”子句。 - NikkyD
2
如果插入操作破坏了某个主键或唯一键约束,则会引发冲突。如果出现冲突,将使用新数据更新导致冲突的唯一键或主键行。 - kdehairy
7
当您具有带有ON DELETE CASCADEFOREIGN KEY约束条件时,使用CONFLICT_REPLACE标志的insertWithOnConflict不正确,因为当发生冲突时,引用该表中主键的其他表中的所有记录将被删除。 - mixel

10

SQLiteDatabase.replace()方法可以实现此功能,它基本上执行以下操作:

insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE);

文档不太清楚,很遗憾。


6
那个操作的名称是“upsert”,我解决它的方法是识别表格中使一行唯一的列。例如:_id,name,job,hours_worked。我们将使用的列是name和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;
}

在你的数据库管理类中:
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)});
}

2
这个问题是关于合并插入和更新函数的功能,而不是关于如何使用它们的问题。 - aleb
1
@aleb 这对我帮助很大。 - Naveed Ahmad

3

SQLiteDatabase.replace()可能是您正在寻找的内容。虽然我没有尝试过,但文档显示它返回新插入行的行ID,因此可能有效。


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,则插入它。
希望这可以帮助。

1

replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues)

文档中描述为...方便的方法,用于替换数据库中的一行。如果该行不存在,则插入一行。

基本上它调用了insertWithOnConflict


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

0

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

你应该先调用update,如果受影响的行数为零,则使用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);
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接