我有一个要插入数据库的字符串列表。它们必须是独一无二的。当我插入时,我希望他们的 ID(用作另一个表中的外键)所以我使用 last_insert_rowid。我有2个问题。
- 如果我使用替换,他们的 ID(整数主键)会更新,这会破坏我的数据库(条目指向不存在的 ID)
- 如果我使用忽略,rowid 不会更新,所以我没有得到正确的 ID
我如何获得他们的身份证?如果我不需要,我不想使用 select 语句来检查并插入字符串(如果它不存在)。我该怎么做?
我有一个要插入数据库的字符串列表。它们必须是独一无二的。当我插入时,我希望他们的 ID(用作另一个表中的外键)所以我使用 last_insert_rowid。我有2个问题。
我如何获得他们的身份证?如果我不需要,我不想使用 select 语句来检查并插入字符串(如果它不存在)。我该怎么做?
当发生 UNIQUE 约束违规时,REPLACE 算法会在插入或更新当前行之前删除导致约束违规的预先存在的行,并且命令会继续正常执行。这会导致 rowid 更改并产生以下问题
Y:> **sqlite3 test**
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> **create table b (c1 integer primary key, c2 text UNIQUE);**
sqlite> **insert or replace into b values (null,'test-1');**
sqlite> **select last_insert_rowid();**
1
sqlite> **insert or replace into b values (null,'test-2');**
sqlite> **select last_insert_rowid();**
2
sqlite> **insert or replace into b values (null,'test-1');**
sqlite> **select last_insert_rowid();**
3
sqlite> **select * from b;**
2|test-2
3|test-1
解决方法是更改 c2 列的定义,如下所示
create table b (c1 integer primary key, c2 text UNIQUE ON CONFLICT IGNORE);
并从您的插入中删除“或替换”子句;
然后在插入后进行测试时,您将需要执行以下 sql:select last_insert_rowid(), changes();
sqlite> **create table b (c1 integer primary key, c2 text UNIQUE ON CONFLICT IGNORE);**
sqlite> **insert into b values (null,'test-1');**
sqlite> **select last_insert_rowid(), changes();**
1|1
sqlite> **insert into b values (null,'test-2');**
sqlite> **select last_insert_rowid(), changes();**
2|1
sqlite> **insert into b values (null,'test-1');**
sqlite> **select last_insert_rowid(), changes();**
2|0
第三次插入后更改的返回值将通知您的应用程序,您需要查找“test-1”的rowid,因为它已经在文件中。当然,如果这是一个多用户系统,您还需要将所有这些都包装在一个事务中。
我目前使用以下
insert into tbl(c_name) select 'val' where not exists(select id from tbl where c_name ='val');
select id from tbl where c_name ='val';
通过“它们必须是唯一的”,它们是否意味着您确定它们是,或者如果它们不是,您想要一个错误作为结果?如果您只是将字符串本身作为其表中的键,那么我不明白 1 或 2 可能是一个问题——如果出现不需要的重复,您将得到一个错误,否则是正确的 ID。也许你可以用你正在使用的 SQL 代码的一个小例子来澄清你的问题,有问题的表,你正在观察什么行为,以及你想要什么行为......?
已编辑:感谢您的编辑,但我仍然不清楚 SQL 给您带来了什么问题!如果您的表来自,例如:
CREATE TABLE Foo(
theid INTEGER PRIMARY KEY AUTOINCREMENT,
aword TEXT UNIQUE ABORT
)
那么任何插入重复单词的尝试都会失败(ABORT
关键字是可选的,因为它是 的默认值UNIQUE
)——这不是你想要的,因为你说“必须是唯一的”,也就是说,如果它们是错误的不是吗?
您的问题的正确答案是:这不能在 sqlite 中完成。您必须进行额外的选择查询。引用last_insert_rowid的文档:
由于违反约束而失败的 INSERT 不是成功的 INSERT,并且不会更改此例程返回的值。因此,当插入失败时,INSERT OR FAIL、INSERT OR IGNORE、INSERT OR ROLLBACK 和 INSERT OR ABORT 不会更改此例程的返回值
在 2022 年遇到同样的问题,但是从 SQLite3 版本 3.35.0 (2021-03-12) 开始,我们有RETURNING。结合UPSERT,现在可以实现这一点
sqlite> create table test (id INTEGER PRIMARY KEY, text TEXT UNIQUE);
sqlite> insert into test(text) values("a") on conflict do update set id = id returning id;
1
sqlite> insert into test(text) values("a") on conflict do update set id = id returning id;
1
sqlite> insert into test(text) values("b") on conflict do update set id = id returning id;
2
sqlite> insert into test(text) values("b") on conflict do update set id = id returning id;
2
sqlite> select * from test;
1|a
2|b
sqlite> insert into test(text) values("b") on conflict do nothing returning id;
sqlite>
可悲的是,这仍然是一种解决方法,而不是一个优雅的解决方案......在冲突中,插入变成了更新。这意味着您的更新触发器将触发,因此您可能希望远离此!
当插入转换为更新时,它需要做一些事情(参见链接)。然而,我们不想做任何事情,所以我们通过id
自身更新来做一个空操作。然后,returning id
给我们想要的东西。
笔记:
on update
会触发。但是没有触发器,它对数据没有影响on conflict do nothing returning id
does not fail, but does not return the id.id
, then this technique does not need any specialization: just copy/paste on conflict do update set id = id returning id;