我有一张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,表示一个错误,然后验证抛出:
INSERT ON CONFLICT IGNORE returned -1 for region name 'Overseas'强调地雷( 关于冲突文件的SQLite )指出:
当发生适用的约束冲突时,忽略解析算法跳过包含约束违反的一行,并继续处理SQL语句的后续行,就好像没有出错一样。包含约束冲突的行之前和之后的其他行通常被插入或更新。使用忽略冲突解决算法时不返回错误。
Android insertWithOnConflict()文档指出:
如果输入参数'conflictAlgorithm‘= CONFLICT_IGNORE或-1 (如果有任何错误),则返回新插入行的行ID或现有行的主键
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?
发布于 2013-02-25 10:26:25
不幸的是,您的问题的答案是,这些文档是完全错误的,并且没有这样的功能。
2010年的一个打开的bug确实解决了这个问题,尽管80+的人也曾在其中担任主角,但安卓团队并没有对此做出官方回应。
问题是也在这里讨论过。
如果您的用例冲突很重(即大多数情况下您希望找到一个现有记录并希望返回该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-22 16:02:32
虽然您对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。
编辑回答第二个问题:
在重新阅读了这个问题之后,我认为您正在寻找的是这样一种方法:
关于替换的整个讨论似乎是一种“红鲱鱼”。
那么,你的第二个问题的答案是,没有这样的功能。
发布于 2015-09-30 05:35:46
问题已经解决了,但这可能是解决我问题的一个选择。只是将最后一个参数更改为CONFLICT_REPLACE。
long regionId =
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_REPLACE);希望能帮上忙。
https://stackoverflow.com/questions/13391915
复制相似问题