我需要针对SQLite数据库执行UPSERT / INSERT或UPDATE。
命令INSERT OR REPLACE在许多情况下可能很有用。但是如果你想让你的id因为外键而自动增量,那它就不起作用了,因为它删除了行,创建了一个新行,因此这个新行有了一个新的ID。
这就是表格:
播放器 - (ID上的主键,唯一的user_name)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
答案 0 :(得分:99)
好吧,经过几个小时的研究和解决问题,我发现有两种方法可以实现这一点,具体取决于你的表的结构,以及你是否激活了外键限制以保持完整性。我希望以干净的格式分享这一点,以便为可能在我的情况下的人们节省一些时间。
换句话说,您没有外键,或者如果您有外键,则配置SQLite引擎,以便不存在完整性异常。要走的路是 INSERT OR REPLACE 。如果您尝试插入/更新其ID已存在的播放器,则SQLite引擎将删除该行并插入您提供的数据。现在的问题是:如何保持旧ID的关联?
假设我们想要 UPSERT ,数据为user_name ='steven'且年龄= 32。
看看这段代码:
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。
在使用之前的解决方案进行调整之后,我意识到在我的情况下最终可能会破坏数据,因为此ID可用作其他表的外键。此外,我使用 ON DELETE CASCADE 子句创建了表,这意味着它将以静默方式删除数据。危险。
所以,我首先想到了一个IF子句,但SQLite只有 CASE 。如果EXISTS(从user_name ='steven'的玩家中选择id),则无法使用此 CASE (或者至少我没有管理它)来执行一个 UPDATE 查询),如果没有,则 INSERT 。不行。
然后,最后我使用了蛮力,并取得了成功。逻辑是,对于您要执行的每个 UPSERT ,首先执行 INSERT或IGNORE 以确保我们的用户有一行,然后执行 UPDATE 查询与您尝试插入的数据完全相同。
与以前相同的数据:user_name ='steven'且年龄= 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所评论的那样,尝试先插入然后更新可能会导致触发触发的频率高于预期。这不是我认为的数据安全问题,但是发起不必要的事件确实没有意义。因此,改进的解决方案是:
-- 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);
答案 1 :(得分:58)
这是一种不需要暴力“忽略”的方法,只有在存在密钥违规的情况下才会有效。这种方式基于您在更新中指定的任何条件。
试试这个......
-- Try to update any existing row
UPDATE players
SET age=32
WHERE user_name='steven';
-- If no update happened (i.e. 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
子句,它可以是你的任何东西定义,而不仅仅是关键违规。
在上面的示例中,如果更新没有更改(即记录不存在),则Changes()
= 0,因此Where
语句中的Insert
子句返回为true,并插入带有指定数据的新行。
如果Update
更新了现有行,那么Changes()
= 1,那么Insert
中的'Where'子句现在将为false因此不会发生插入。
不需要蛮力。
答案 2 :(得分:36)
这是一个迟到的答案。从2018年6月4日发布的SQLIte 3.24.0开始,最终在PostgreSQL语法之后支持UPSERT子句。
INSERT INTO players (user_name, age)
VALUES('steven', 32)
ON CONFLICT(user_name)
DO UPDATE SET age=excluded.age;
注意:对于那些必须使用早于3.24.0的SQLite版本的用户,请参考下面的this answer(由我发布,@ MarqueIV)。
但是,如果您确实有升级选项,那么强烈建议这样做,因为与我的解决方案不同,此处发布的那个在单个语句中实现了所需的行为。此外,您还可以获得最近发布的所有其他功能,改进和错误修复。
答案 3 :(得分:23)
所有提出的答案的问题完全没有考虑到触发器(可能还有其他副作用)。 解决方案如
INSERT OR IGNORE ...
UPDATE ...
导致在行不存在时执行两个触发器(用于插入然后用于更新)。
正确的解决方案是
UPDATE OR IGNORE ...
INSERT OR IGNORE ...
在这种情况下,只执行一个语句(当行存在或不存在时)。
答案 4 :(得分:4)
要有一个没有孔的纯粹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);
答案 5 :(得分:2)
您也可以在您的user_name唯一约束中添加一个ON CONFLICT REPLACE子句,然后只需要INSERT,将其留给SQLite以确定在发生冲突时该怎么做。请参阅:https://sqlite.org/lang_conflict.html。
还要注意关于删除触发器的句子:当REPLACE冲突解决策略删除行以满足约束时,当且仅当启用了递归触发器时,删除触发器才会触发。
答案 6 :(得分: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';
注意:我还没有对它进行测试,看看哪些触发器被调用,但我假设以下内容:
以这种方式你可以有一个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。
答案 7 :(得分:-3)
接受的答案不正确
由于其2个查询
只有它的复杂!
这是简单的2查询:
background: rgba( 0, 0, 0, 0.0 );
color: rgba( 0, 0, 0, 0.0 );
caret-color: rgba( 0, 0, 0, 1.0 );