6

我有一个要插入数据库的字符串列表。它们必须是独一无二的。当我插入时,我希望他们的 ID(用作另一个表中的外键)所以我使用 last_insert_rowid。我有2个问题。

  1. 如果我使用替换,他们的 ID(整数主键)会更新,这会破坏我的数据库(条目指向不存在的 ID)
  2. 如果我使用忽略,rowid 不会更新,所以我没有得到正确的 ID

我如何获得他们的身份证?如果我不需要,我不想使用 select 语句来检查并插入字符串(如果它不存在)。我该怎么做?

4

5 回答 5

10

当发生 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,因为它已经在文件中。当然,如果这是一个多用户系统,您还需要将所有这些都包装在一个事务中。

于 2011-01-27T20:32:57.577 回答
3

我目前使用以下

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';
于 2011-01-27T21:50:40.753 回答
1

通过“它们必须是唯一的”,它们是否意味着您确定它们是,或者如果它们不是,您想要一个错误作为结果?如果您只是将字符串本身作为其表中的键,那么我不明白 1 或 2 可能是一个问题——如果出现不需要的重复,您将得到一个错误,否则是正确的 ID。也许你可以用你正在使用的 SQL 代码的一个小例子来澄清你的问题,有问题的表,你正在观察什么行为,以及你想要什么行为......?

已编辑:感谢您的编辑,但我仍然不清楚 SQL 给您带来了什么问题!如果您的表来自,例如:

CREATE TABLE Foo(
  theid INTEGER PRIMARY KEY AUTOINCREMENT,
  aword TEXT UNIQUE ABORT
  )

那么任何插入重复单词的尝试都会失败(ABORT关键字是可选的,因为它是 的默认值UNIQUE)——这不是你想要的,因为你说“必须是唯一的”,也就是说,如果它们是错误的不是吗?

于 2009-05-10T08:27:07.653 回答
0

您的问题的正确答案是:这不能在 sqlite 中完成。您必须进行额外的选择查询。引用last_insert_rowid的文档:

由于违反约束而失败的 INSERT 不是成功的 INSERT,并且不会更改此例程返回的值。因此,当插入失败时,INSERT OR FAIL、INSERT OR IGNORE、INSERT OR ROLLBACK 和 INSERT OR ABORT 不会更改此例程的返回值

于 2021-01-24T10:34:20.197 回答
0

在 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给我们想要的东西。

笔记:

  • 我们的 no-op 实际上会进行更新,因此会花费时间,并且触发器on update会触发。但是没有触发器,它对数据没有影响
  • Using on conflict do nothing returning id does not fail, but does not return the id.
  • If usable (again, check your triggers), and if all your tables use the primary key id, then this technique does not need any specialization: just copy/paste on conflict do update set id = id returning id;
于 2022-02-05T13:08:55.907 回答