Sqlite插入到,使用唯一的名称,获取id

8ljdwjyq  于 2023-05-18  发布在  SQLite
关注(0)|答案(5)|浏览(186)

我有一个要插入数据库的字符串列表。它们必须是独一无二的。当我插入时,我希望他们的ID(在另一个表中用作外键),所以我使用last_insert_rowid。我有两个问题。
1.如果我使用replace,它们的id(INTEGER PRIMARY KEY)会更新,这会破坏我的数据库(条目指向不存在的id)
1.如果我使用ignore,rowid不会更新,所以我不会得到正确的ID
我怎么知道他们的身份?如果我不需要,我不想使用select语句来检查和插入字符串,如果它不存在。我该怎么做?

vjrehmav

vjrehmav1#

当发生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);

并从insert s中删除or replace子句;
然后当你在insert之后测试时,你需要执行以下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

第三个insert之后的更改返回值将通知您的应用程序,您需要查找"test-1"rowid,因为它已经在文件中。当然,如果这是一个多用户系统,您还需要将所有这些都 Package 在一个事务中。

icomxhvb

icomxhvb2#

我现在用的是下面的

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';
mtb9vblg

mtb9vblg3#

通过“它们必须是唯一的”,它们是指您确定它们是唯一的,还是您希望在它们不是唯一的情况下出现错误?如果只是将字符串本身作为其表中的键,那么我不明白1或2怎么会有问题--如果出现不必要的重复,您将得到所需的错误,否则将得到正确的ID。也许你可以用一个你正在使用的SQL代码的小例子来澄清你的问题,有问题的表,你正在观察什么行为,你想要什么行为?
编辑:谢谢编辑,但我仍然不清楚SQL给你带来了什么问题!如果您的表来自,例如:

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

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

olmpazwi

olmpazwi4#

你问题的正确答案是:这在sqlite中无法完成。您必须进行额外的选择查询。引用last_insert_rowid的docs
由于违反约束而失败的INSERT不是成功的INSERT,并且不会更改此例程返回的值。因此,INSERT OR FAIL、INSERT OR IGNORE、INSERT OR ROLLBACK和INSERT OR ABORT在插入失败时不会更改此例程的返回值

nzkunb0c

nzkunb0c5#

在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>

可悲的是,这仍然是一个变通办法,而不是一个优雅的解决方案...发生冲突时,插入将成为更新。这意味着您的更新触发器将触发,因此您可能希望远离此!
当插入转换为更新时,它需要做一些事情(cf链接)。然而,我们不想做任何事情,所以我们通过用自身更新id来做无操作。然后,returning id给了我们想要的。
注意事项:

  • 我们的no-op实际上会进行更新,因此需要花费时间,并且触发器on update将被触发。但是没有触发器,它对数据没有任何影响
  • 使用on conflict do nothing returning id不会失败,但不会返回id。
  • 如果可用(再次检查您的触发器),并且如果所有表都使用主键id,则此技术不需要任何专门化:只需复制/粘贴on conflict do update set id = id returning id;

相关问题