22

私はSQLiteテーブルを持っています:

CREATE TABLE regions (_id INTEGER PRIMARY KEY, name TEXT, UNIQUE(name));

そしていくつかの Android コード:

Validate.notBlank(region);
ContentValues cv = new ContentValues();
cv.put(Columns.REGION_NAME, region);
long regionId = 
    db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_IGNORE);
Validate.isTrue(regionId > -1,
    "INSERT ON CONFLICT IGNORE returned -1 for region name '%s'", region);

重複行では、insertWithOnConflict() は -1 を返し、エラーを示し、Validateは次のようにスローします。

INSERT ON CONFLICT IGNORE returned -1 for region name 'Overseas'

SQLite ON CONFLICT ドキュメント(強調鉱山) には次のように記載されています。

該当する制約違反が発生すると、IGNORE 解決アルゴリズムは制約違反を含む 1 行をスキップし、何も問題がなかったかのように SQL ステートメントの後続の行の処理を続行します。制約違反を含む行の前後の他の行は、正常に挿入または更新されます。IGNORE 競合解決アルゴリズムが使用されている場合、エラーは返されません。

AndroidのinsertWithOnConflict() ドキュメントには次のように記載されています。

新しく挿入された行の行 ID、または入力パラメーター 'conflictAlgorithm' = CONFLICT_IGNORE の場合は既存の行の主キー、またはエラーが発生した場合は -1 を返します。

CONFLICT_REPLACE はオプションではありません。行を置き換えると、既存のキーを返すだけでなく主キーが変更されるためです。

sqlite> INSERT INTO regions (name) VALUES ("Southern");
sqlite> INSERT INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
2|Overseas
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
3|Overseas
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas");
sqlite> SELECT * FROM regions;
1|Southern
4|Overseas

insertWithOnConflict() は、重複する行に対して、重複する行の主キー (_id 列) を返す必要があると思います。したがって、この挿入でエラーが発生することはありません。insertWithOnConflict() がエラーをスローするのはなぜですか? 常に有効な行 ID を取得するには、どの関数を呼び出す必要がありますか?

4

3 に答える 3

31

残念ながら、あなたの質問に対する答えは、ドキュメントが単に間違っており、そのような機能がないということです。

この問題に正確に対処する 2010 年の未解決のバグがあり、80 人以上がスターを付けていますが、Android チームからの正式な回答はありません。

この問題はSO here でも議論されています

ユースケースが競合が多い場合 (つまり、ほとんどの場合、既存のレコードを見つけてその ID を返したい場合)、提案された回避策が適しているようです。一方、ほとんどの場合、既存のレコードがないと予想されるユース ケースの場合は、次の回避策がより適切である可能性があります。

try {
  insertOrThrow(...)
} catch(SQLException e) {
  // Select the required record and get primary key from it
} 

この回避策の自己完結型の実装を次に示します。

public static long insertIgnoringConflict(SQLiteDatabase db,
                                          String table,
                                          String idColumn,
                                          ContentValues values) {
    try {
        return db.insertOrThrow(table, null, values);
    } catch (SQLException e) {
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT ");
        sql.append(idColumn);
        sql.append(" FROM ");
        sql.append(table);
        sql.append(" WHERE ");

        Object[] bindArgs = new Object[values.size()];
        int i = 0;
        for (Map.Entry<String, Object> entry: values.valueSet()) {
            sql.append((i > 0) ? " AND " : "");
            sql.append(entry.getKey());
            sql.append(" = ?");
            bindArgs[i++] = entry.getValue();
        }

        SQLiteStatement stmt = db.compileStatement(sql.toString());
        for (i = 0; i < bindArgs.length; i++) {
            DatabaseUtils.bindObjectToProgram(stmt, i + 1, bindArgs[i]);
        }

        try {
            return stmt.simpleQueryForLong();
        } finally {
            stmt.close();
        }
    }
}
于 2013-02-25T10:26:25.180 に答える
3

insertWithOnConflict の動作に対する期待は完全に妥当に思えますが (衝突している行の pk を取得する必要があります)、それがどのように機能するかではありません。実際に何が起こるかというと、挿入を試み、行の挿入に失敗するがエラーを通知せず、フレームワークが挿入された行の数をカウントし、その数が 0 であることを発見し、明示的に -1 を返します。

追加するために編集:

ところで、この回答は、最終的にinsertWithOnConflictを実装するコードに基づいています:

int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
        ? sqlite3_last_insert_rowid(connection->db) : -1;

SQLITE_DONE良い状態です。sqlite3_changesは、最後の呼び出しでの挿入の数でありsqlite3_last_insert_rowid、新しく挿入された行の行 ID です (存在する場合)。

2番目の質問に答えるために編集:

質問を読み直した後、あなたが探しているのはこれを行う方法だと思います:

  • 可能であれば、データベースに新しい行を挿入します
  • 行を挿入できない場合は失敗し、競合した既存の行の行 ID を返します (その行 ID は変更されません)。

replace の全体的な議論は、ニシンのように思えます。

2 番目の質問に対する答えは、そのような関数は存在しないということです。

于 2013-02-22T16:02:32.903 に答える
-1

問題はすでに解決されていますが、これは私の問題を解決したオプションである可能性があります。最後のパラメーターを CONFLICT_REPLACE に変更するだけです。

long regionId = 
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_REPLACE);

それが役に立てば幸い。

于 2015-09-30T05:35:46.487 に答える