草庐IT

安卓/SQLite : Insert-Update table columns to keep the identifier

coder 2023-06-07 原文

目前,我正在使用以下语句在 Android 设备上的 SQLite 数据库中创建表。

CREATE TABLE IF NOT EXISTS 'locations' (
  '_id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT, 
  'latitude' REAL, 'longitude' REAL, 
  UNIQUE ( 'latitude',  'longitude' ) 
ON CONFLICT REPLACE );

最后的冲突子句导致在完成具有相同坐标的新插入时删除行。 SQLite documentation包含有关冲突子句的更多信息。

相反,我想保留之前的行并更新它们的列。在 Android/SQLite 环境中执行此操作的最有效方法是什么?

  • 作为 CREATE TABLE 语句中的冲突子句。
  • 作为 INSERT 触发器。
  • 作为 ContentProvider#insert 方法中的条件子句。
  • ...你可以考虑的更好

我认为在数据库中处理此类冲突会更高效。此外,我发现很难重写 ContentProvider#insert 方法来考虑 insert-update 场景。下面是 insert 方法的代码:

public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    long id = db.insert(DatabaseProperties.TABLE_NAME, null, values);
    return ContentUris.withAppendedId(uri, id);
}

当数据从后端到达时,我所做的只是插入数据,如下所示。

getContentResolver.insert(CustomContract.Locations.CONTENT_URI, contentValues);

我无法确定如何在此处应用对 ContentProvider#update 的替代调用。此外,无论如何,这不是我喜欢的解决方案。


编辑:

@CommonsWare:我尝试实现您使用 INSERT OR REPLACE 的建议。我想出了这段丑陋的代码。

private static long insertOrReplace(SQLiteDatabase db, ContentValues values, String tableName) {
    final String COMMA_SPACE = ", ";
    StringBuilder columnsBuilder = new StringBuilder();
    StringBuilder placeholdersBuilder = new StringBuilder();
    List<Object> pureValues = new ArrayList<Object>(values.size());
    Iterator<Entry<String, Object>> iterator = values.valueSet().iterator();
    while (iterator.hasNext()) {
        Entry<String, Object> pair = iterator.next();
        String column = pair.getKey();
        columnsBuilder.append(column).append(COMMA_SPACE);
        placeholdersBuilder.append("?").append(COMMA_SPACE);
        Object value = pair.getValue();
        pureValues.add(value);
    }
    final String columns = columnsBuilder.substring(0, columnsBuilder.length() - COMMA_SPACE.length());
    final String placeholders = placeholderBuilder.substring(0, placeholdersBuilder.length() - COMMA_SPACE.length());
    db.execSQL("INSERT OR REPLACE INTO " + tableName + "(" + columns + ") VALUES (" + placeholders + ")", pureValues.toArray());

    // The last insert id retrieved here is not safe. Some other inserts can happen inbetween.
    Cursor cursor = db.rawQuery("SELECT * from SQLITE_SEQUENCE;", null);
    long lastId = INVALID_LAST_ID;
    if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
        lastId = cursor.getLong(cursor.getColumnIndex("seq"));
    }
    cursor.close();
    return lastId;
}

但是,当我检查 SQLite 数据库时,相同的列仍会被删除并插入新的 ID。我不明白为什么会发生这种情况,并认为原因是我的冲突条款。但是 documentation正好相反。

The algorithm specified in the OR clause of an INSERT or UPDATE overrides any algorithm specified in a CREATE TABLE. If no algorithm is specified anywhere, the ABORT algorithm is used.

此尝试的另一个缺点是您丢失了插入语句返回的 id 的值。为了弥补这一点,我终于找到了一个要求 last_insert_rowid 的选项。正如解释 in the posts of dtmilano and swiz .但是,我不确定这是否安全,因为中间可能会发生另一个插入。

最佳答案

我可以理解这样一种观念,即在 SQL 中执行所有这些逻辑对性能来说是最好的,但在这种情况下,也许最简单(最少代码)的解决方案是最好的解决方案?为什么不先尝试更新,然后使用 insertWithOnConflict()CONFLICT_IGNORE 进行插入(如果需要)并获取您需要的行 id:

public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    String selection = "latitude=? AND longitude=?"; 
    String[] selectionArgs = new String[] {values.getAsString("latitude"),
                values.getAsString("longitude")};

    //Do an update if the constraints match
    db.update(DatabaseProperties.TABLE_NAME, values, selection, null);

    //This will return the id of the newly inserted row if no conflict
    //It will also return the offending row without modifying it if in conflict
    long id = db.insertWithOnConflict(DatabaseProperties.TABLE_NAME, null, values, CONFLICT_IGNORE);        

    return ContentUris.withAppendedId(uri, id);
}

一个更简单的解决方案是检查 update() 的返回值,并且仅在受影响的计数为零时才进行插入,但是会出现无法获取 id 的情况没有额外选择的现有行。这种插入形式将始终返回正确的 id 以在 Uri 中传回,并且不会过多地修改数据库。

如果您想一次执行大量这些操作,您可以查看提供程序上的 bulkInsert() 方法,您可以在单个事务中运行多个插入。在这种情况下,由于您不需要返回更新记录的 id,因此“更简单”的解决方案应该可以正常工作:

public int bulkInsert(Uri uri, ContentValues[] values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    String selection = "latitude=? AND longitude=?";
    String[] selectionArgs = null;

    int rowsAdded = 0;
    long rowId;
    db.beginTransaction();
    try {
        for (ContentValues cv : values) {
            selectionArgs = new String[] {cv.getAsString("latitude"),
                cv.getAsString("longitude")};

            int affected = db.update(DatabaseProperties.TABLE_NAME, 
                cv, selection, selectionArgs);
            if (affected == 0) {
                rowId = db.insert(DatabaseProperties.TABLE_NAME, null, cv);
                if (rowId > 0) rowsAdded++;
            }
        }
        db.setTransactionSuccessful();
    } catch (SQLException ex) {
        Log.w(TAG, ex);
    } finally {
        db.endTransaction();
    }

    return rowsAdded;
}

事实上,事务代码通过最小化数据库内存写入文件的次数来使事情变得更快,bulkInsert() 只允许多个 ContentValues通过对提供者的一次调用来传递。

关于安卓/SQLite : Insert-Update table columns to keep the identifier,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11686645/

有关安卓/SQLite : Insert-Update table columns to keep the identifier的更多相关文章

随机推荐