Android / SQLite:插入更新表列以保留标识符

问题描述 投票:25回答:6

当前,我正在使用以下语句在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 );

结尾处的冲突条款导致当完成具有相同坐标的新插入时,行被droppedSQLite documentation包含有关冲突条款的更多信息。

相反,我想保留前几行,而只是[[update他们的列。在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则相反。

    在INSERT或UPDATE的OR子句中指定的算法

    覆盖
    在CREATE TABLE中指定的任何算法。如果没有算法 在任何地方指定,都会使用ABORT算法。

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

    android sqlite android-contentprovider insert-update
    6个回答
    19
    投票
    如果要一次执行大量操作,则可以查看提供程序上的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传递给提供者。 >        

    5
    投票
    触发器:

    CREATE TRIGGER update_location INSTEAD OF INSERT ON locations_view FOR EACH ROW BEGIN INSERT OR REPLACE INTO locations (_id, name, latitude, longitude) VALUES ( COALESCE(NEW._id, (SELECT _id FROM locations WHERE latitude = NEW.latitude AND longitude = NEW.longitude)), NEW.name, NEW.latitude, NEW.longitude ); END;

    而不是插入locations表中,而是插入locations_view视图中。触发器将通过使用子选择来提供正确的_id值。如果由于某种原因,插入内容已经包含_id,则COALESCE将保留它并覆盖表中现有的一个。

    您可能想要检查子选择对性能的影响程度,并将其与您可能进行的其他可能的更改进行比较,但是它确实使您可以将此逻辑保留在代码之外。

    我尝试了其他一些基于INSERT或IGNORE的涉及表本身的触发器的解决方案,但似乎BEFORE和AFTER触发器仅在实际插入表中时才触发。

    您可能会发现this answer有帮助,这是触发的基础。

    编辑:由于在忽略某个插入时(然后可以对其进行更新)时,不会触发BEFORE和AFTER触发器,因此我们需要使用INSTEAD OF触发器重写该插入。不幸的是,那些不适用于表-我们必须创建一个视图才能使用它。


    3
    投票
    [如果您仍要保留原始行,则意味着您要保留相同的ON CONFLICT,您将不得不进行两次查询,第一个查询用于插入行,第二个查询如果插入失败则更新行(反之亦然) )。为了保持一致性,您必须在事务中执行查询。

    _id



    0
    投票

    -2
    投票
    © www.soinside.com 2019 - 2024. All rights reserved.