目前,我正在使用以下语句在 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/