如果不存在则进行更新吗?


275

我已经找到了一些经典的“如何插入新记录或如果已经存在就更新一条记录”的“可能”解决方案,但是我无法在SQLite中使用它们。

我有一个定义如下的表:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

我想做的是添加一个具有唯一名称的记录。如果名称已经存在,我想修改这些字段。

有人可以告诉我该怎么做吗?


3
“插入或替换” 与“插入或更新” 完全不同
Fattie

Answers:


320

看看http://sqlite.org/lang_conflict.html

您想要类似:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

请注意,如果表中已存在该行,则不在插入列表中的任何字段都将设置为NULL。这就是为什么该ID列有一个子选择的原因:在替换情况下,该语句会将其设置为NULL,然后分配新的ID。

如果要在替换情况下将行保留为空,而在插入情况下将字段设置为NULL,则也可以使用此方法。

例如,假设您想离开Seen

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));

109
错误的“插入或替换”与“插入或更新”不同。对于一个有效的答案,请参阅stackoverflow.com/questions/418898/...
RDS

12
@rds不,这没错,因为这个问题说“修改字段”,并且主键不是列列表的一部分,但其他所有字段都是。如果您遇到无法替换所有字段值的极端情况,或者如果您弄乱了主键,则应该做一些不同的事情。如果您拥有完整的新字段集,则此方法很好。您有我看不到的特定问题吗?
2012年

9
如果您知道所有字段的所有新值,则它是有效的。如果用户仅更新(例如),Level则无法遵循此方法。
rds 2012年

4
正确的。另一个答案是相关的,但是这种方法对于更新所有字段(不包括键)有效。
ЯрославРахматуллин

2
是的这是完全错误的。如果只是我想从新的冲突更新单列值,将会发生什么。在上述情况下,所有其他数据将被不正确的新数据替换。
Mrug 2014年

85

您应该在INSERT OR IGNORE命令之后使用UPDATE命令:在下面的示例中name是主键:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

第一条命令将插入记录。如果记录存在,它将忽略与现有主键冲突引起的错误。

第二个命令将更新记录(现在肯定存在)


6
在什么时候它将忽略?什么时候名字和年龄都一样?
谅解备忘录

这应该是解决方案……如果您在插入时使用任何触发器,则每次都会触发接受的答案。这不会并且仅执行更新
PodTech.io

1
它仅基于名称忽略。请记住,只有“名称”列是主键。
加布里埃尔·费雷尔

3
当记录是新记录时,则没有必要进行更新,但是无论如何都会执行更新,从而导致性能下降?
MarcG '17

如何为此执行准备好的语句?
令人沮丧的

65

您需要在表上设置约束以触发“ 冲突 ”,然后通过执行替换来解决:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

然后,您可以发出:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

“ SELECT * FROM data”将为您提供:

2|2|2|3.0
3|1|2|5.0

请注意,data.id为“ 3”而不是“ 1”,因为REPLACE执行DELETE和INSERT操作,而不是UPDATE操作。这也意味着您必须确保定义所有必要的列,否则将获得意外的NULL值。


33

首先更新它。如果受影响的行数 = 0,则将其插入。它是最简单且适用于所有RDBMS的


11
无论数据库如何,在正确的隔离级别上对事务进行两个操作都应该没问题。
2010年

5
Insert or Replace确实更可取。
MPelletier'9

1
我真的希望IT文档包含更多示例。我已经在下面尝试过了,但是它不起作用(我的语法显然是错误的)。有什么想法吗?在冲突替代品上插入书本(名称,TypeID,等级,可见)VALUES('超人','2','14','0')值('超人',' 2','14','0')
SparkyNZ'9

6
同样,如果行值完全相同,那么受影响的行数将为零,并且将创建一个新的重复行。
barkside 2012年

2
+1,因为INSERT OR REPLACE将在冲突时删除原始行,并且如果您未设置所有列,则将丢失原始值
Pavel Machyniak

20

INSERT OR REPLACE 将其他字段替换为默认值。

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

如果要保留其他字段

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

或使用UPSERT(语法已添加到SQLite版本3.24.0(2018-06-04)中)

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

excluded.前缀等于价值VALUES


16

Upsert是您想要的。UPSERT语法已添加到SQLite版本3.24.0(2018-06-04)中。

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

请注意,此时实际的单词“ UPSERT”不是upsert语法的一部分。

正确的语法是

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

如果您正在执行INSERT INTO SELECT ...选择,则至少需要使用连接语法WHERE true解决有关令牌的解析器歧义ON

请注意,INSERT OR REPLACE...如果必须替换一条新记录,它将在插入之前删除该记录,如果您有外键级联或其他删除触发器,则可能会很糟糕。


它存在于文档中,并且我具有最新版本的sqlite,即3.25.1,但对我不起作用
Bentaiba Miled Basma,

您在逐字记录上方尝试了这两个查询吗?
Joecool同志'18

请注意,Ubuntu 18.04附带了SQLite3 v3.22而不是3.25,因此它不支持UPSERT语法。
starbeamrainbowlabs

感谢提供该功能的参考版本!
Matteo

6

如果没有主键,可以插入(如果不存在),然后进行更新。该表必须包含至少一个条目,然后才能使用。

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';

这是唯一不创建重复条目的解决方案。可能是因为我使用的表没有主键,并且有2列没有默认值。即使这是一个冗长的解决方案,它也能正确完成工作并按预期工作。
Inbar Rose

4

我相信你想要 UPSERT

答案中没有其他技巧的“ INSERT OR REPLACE”会将您未指定的所有字段重置为NULL或其他默认值。(这种INSERT OR REPLACE的行为与UPDATE不同;它与INSERT完全一样,因为它实际上是INSERT;但是,如果您想要的是UPDATE-if-exists,则可能需要UPDATE语义,并且对实际结果感到不满意。)

建议的UPSERT实现的技巧基本上是使用INSERT或REPLACE,但是使用嵌入式SELECT子句检索所有您不想更改的字段的当前值来指定所有字段。


1

我认为值得指出的是,如果您不完全了解PRIMARY KEYUNIQUE的交互方式,则可能会出现一些意外行为。

例如,如果您只想在当前未使用NAME字段的情况下插入一条记录,并且如果要插入该约束,则希望触发约束异常来告知您,则INSERT OR REPLACE不会引发异常,而是通过替换冲突记录(具有相同NAME的现有记录)来解决UNIQUE约束本身。加斯帕德(Gaspard)的回答确实很好地证明了这一点。

如果要触发约束异常,则必须使用INSERT语句,并在知道未使用名称的情况下依靠单独的UPDATE命令来更新记录。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.