SQLite UPSERT /更新或插入

我需要对SQLite数据库执行UPSERT / INSERT OR UPDATE。

INSERT OR REPLACE命令在很多情况下是有用的。 但是,如果你想保留你的id与autoincrement适当的地方由于外键,它不起作用,因为它删除行,创build一个新的,因此这个新行有一个新的ID。

这将是表格:

玩家 – (主键在ID,用户名唯一)

| id | user_name | age | ------------------------------ | 1982 | johnny | 23 | | 1983 | steven | 29 | | 1984 | pepee | 40 | 

我会做得更好,不需要暴力的“忽略”,只有在违规的情况下才会有效。 这种方式基于您在更新中指定的任何条件。

尝试这个…

 -- Try to update any existing row UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; -- If no update happened (ie the row didn't exist) then insert one INSERT INTO players (user_name, age) SELECT 'steven', 32 WHERE (Select Changes() = 0); 

怎么运行的

这里的“魔术”是使用Where (Select Changes() = 0)子句来确定是否有插入的行,并且由于它基于你自己的Where子句,所以它可以用于你定义的任何东西,而不是只是重点违规。

在以上示例中,如果没有更新(即logging不存在),则Changes() = 0,因此Insert语句中的Where子句返回true,并插入指定数据的新行。

如果Update 没有更新现有的行,那么Changes() = 1,所以Insert的Where子句现在是false,因此不会发生插入。

没有蛮力的需要。

问答风格

那么经过几个小时的研究和解决问题之后,我发现有两种方法可以实现这一点,具体取决于表的结构,以及是否激活了外键限制来保持完整性。 我想用一个干净的格式来分享这个东西,以节省一些时间给可能在我的情况下的人。

选项1:您可以负担删除行

换句话说,你没有外键,或者如果你有他们,你的SQLite引擎被configuration为没有完整性exception。 要走的路是INSERT OR REPLACE 。 如果您试图插入/更新已存在ID的播放器,SQLite引擎将删除该行并插入您提供的数据。 现在的问题来了:如何保持旧ID关联?

假设我们想用数据user_name ='steven'和年龄= 32来UPSERT

看看这个代码:

 INSERT INTO players (id, name, age) VALUES ( coalesce((select id from players where user_name='steven'), (select max(id) from drawings) + 1), 32) 

诀窍在于凝聚。 它返回用户'steven'的id(如果有的话),否则返回一个新鲜的id。

选项2:您不能删除该行

在和前面的解决scheme混淆之后,我意识到,在我的情况下,最终会破坏数据,因为这个ID作为其他表的外键。 另外,我用ON DELETE CASCADE子句创build了表,这意味着它会默默删除数据。 危险的。

所以,我首先想到了一个IF子句,但SQLite只有CASE 。 如果EXISTS(从user_name ='steven'的玩家中selectid),这个CASE不能用(或者至less我没有pipe理它)执行一个UPDATE查询,如果没有,则INSERT 。 不行。

最后,我用了蛮力,成功了。 逻辑上,对于每个要执行的UPSERT ,首先执行INSERT或IGNORE以确保与我们的用户有一行,然后使用您尝试插入的完全相同的数据执行UPDATE查询。

与以前相同的数据:user_name ='steven'和age = 32。

 -- make sure it exists INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); -- make sure it has the right data UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; 

就这样!

编辑

正如Andy所评论的,试图先插入然后更新可能导致触发器的触发次数超过预期。 这在我看来并不是一个数据安全问题,但是发射不必要的事件确实没有什么意义。 因此,改进的解决scheme将是:

 -- Try to update any existing row UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; -- Make sure it exists INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

所有提出的问题的答案完全没有考虑到触发器(可能是其他副作用)。 像

 INSERT OR IGNORE ... UPDATE ... 

导致这两个触发器执行(插入,然后更新)行不存在时。

正确的解决scheme是

 UPDATE OR IGNORE ... INSERT OR IGNORE ... 

在这种情况下,只有一个语句被执行(当行存在与否)。

要有一个没有漏洞的纯粹的UPSERT(对于程序员来说),它们不会在唯一键和其他键上进行中继:

 UPDATE players SET user_name="gil", age=32 WHERE user_name='george'; SELECT changes(); 

SELECT changes()将返回上一次查询完成的更新次数。 然后检查来自changes()的返回值是否为0,如果是的话执行:

 INSERT INTO players (user_name, age) VALUES ('gil', 32); 

选项1:插入 – >更新

如果你想避免changes()=0INSERT OR IGNORE即使你不能删除行 – 你可以使用这个逻辑;

首先, 插入 (如果不存在),然后通过使用唯一密钥进行过滤来进行更新

 -- Table structure CREATE TABLE players ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name VARCHAR (255) NOT NULL UNIQUE, age INTEGER NOT NULL ); -- Insert if NOT exists INSERT INTO players (user_name, age) SELECT 'johnny', 20 WHERE NOT EXISTS (SELECT 1 FROM players WHERE user_name='johnny' AND age=20); -- Update (will affect row, only if found) -- no point to update user_name to 'johnny' since it's unique, and we filter by it as well UPDATE players SET age=20 WHERE user_name='johnny'; 

关于触发器

注意:我没有testing过它看到哪个触发器被调用,但我假设如下:

如果行不存在

  • 插入之前
  • INSERT使用INSTEAD OF
  • 插入后
  • 在更新之前
  • 更新使用INSTEAD
  • 后更新

如果行存在

  • 在更新之前
  • 更新使用INSTEAD
  • 后更新

选项2:插入或replace – 保留您自己的ID

这样你可以有一个单一的SQL命令

 -- Table structure CREATE TABLE players ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name VARCHAR (255) NOT NULL UNIQUE, age INTEGER NOT NULL ); -- Single command to insert or update INSERT OR REPLACE INTO players (id, user_name, age) VALUES ((SELECT id from players WHERE user_name='johnny' AND age=20), 'johnny', 20); 

编辑:添加选项2。