SQLite UPSERT / UPDATE或INSERT

时间:2013-03-07 17:06:53

标签: database sqlite upsert

我需要针对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    |

8 个答案:

答案 0 :(得分:99)

Q& A Style

好吧,经过几个小时的研究和解决问题,我发现有两种方法可以实现这一点,具体取决于你的表的结构,以及你是否激活了外键限制以保持完整性。我希望以干净的格式分享这一点,以便为可能在我的情况下的人们节省一些时间。


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

换句话说,您没有外键,或者如果您有外键,则配置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。


选项2:您无法负担删除行

在使用之前的解决方案进行调整之后,我意识到在我的情况下最终可能会破坏数据,因为此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)

选项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';

关于触发器

注意:我还没有对它进行测试,看看哪些触发器被调用,但我假设以下内容:

如果行不存在

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

如果行存在

  • 更新前
  • 使用INSTEAD OF更新
  • 更新后

选项2:插入或替换 - 保留您自己的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。

答案 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 );


  • query是一个函数,例如