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()=0
和INSERT 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。