我有一个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 documentation(强调我的)陈述:
当发生适用的约束违规时,IGNORE解析算法会跳过包含约束违规的一行,并继续处理SQL语句的后续行,就像没有出错一样。包含约束违例的行之前和之后的其他行将正常插入或更新。 使用IGNORE冲突解决算法时不会返回错误。
Android insertWithOnConflict() documentation州:
返回 如果输入参数'conflictAlgorithm'= CONFLICT_IGNORE则为新插入行的行ID或现有行的主键,如果有任何错误则为-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?
答案 0 :(得分:31)
遗憾的是,您的问题的答案是文档完全错误,并且没有此类功能。
有an open bug from 2010正好解决了这个问题,即使已有80多人加注,但Android团队也没有正式回复。
如果您的用例存在冲突(即大多数情况下您希望找到现有记录并希望返回该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();
}
}
}
答案 1 :(得分: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
是新插入行的rowid(如果有)。
编辑回答第二个问题:
重新阅读问题之后,我认为您正在寻找的是一种方法:
替换的整个讨论看起来像红鲱鱼。
第二个问题的答案是,没有这样的功能。
答案 2 :(得分:-2)
问题已经解决,但这可能是解决我的问题的一个选择。只需将最后一个参数更改为CONFLICT_REPLACE。
long regionId =
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
希望它有所帮助。