【问题标题】:INSERT IF NOT EXISTS ELSE UPDATE?如果不存在则插入其他更新?
【发布时间】:2011-04-07 19:00:26
【问题描述】:

我为经典的“如何插入新记录或更新已存在的记录”找到了一些“将成为”解决方案,但我无法让它们中的任何一个在 SQLite 中工作。

我有一个表定义如下:

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

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

谁能告诉我怎么做?

【问题讨论】:

  • “插入或替换”与“插入或更新”完全不同
  • UPSERT 怎么样? ??????

标签: sqlite insert exists upsert merge-conflict-resolution


【解决方案1】:

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

【讨论】:

  • 两个操作在正确隔离级别的事务应该没有问题,不管数据库。
  • Insert or Replace 确实更可取。
  • 我真的希望 IT 文档包含更多示例。我在下面尝试过,但它不起作用(我的语法显然是错误的)。关于它应该是什么的任何想法?插入书本(名称、类型 ID、级别、已见)值(“超人”、“2”、“14”、“0”)冲突替换书(名称、类型 ID、级别、已见)值(“超人”、“ 2', '14', '0')
  • 如果行值完全相同,那么受影响的行数将为零,并且将创建一个新的重复行。
  • +1,因为 INSERT OR REPLACE 会在冲突时删除原始行,如果您没有设置所有列,则会丢失原始值
【解决方案2】:

看看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"));

【讨论】:

  • 错误的“插入或替换”不同于“插入或更新”。如需有效答案,请参阅stackoverflow.com/questions/418898/…
  • @rds 不,这没有错,因为这个问题说“修改字段”并且主键不是列列表的一部分,但所有其他字段都是。如果您将遇到不替换所有字段值的极端情况,或者如果您正在搞乱主键,那么您应该做一些不同的事情。如果你有一套完整的新领域,这种方法就很好。您有我看不到的具体问题吗?
  • 如果您知道所有字段的所有新值,则有效。如果用户只更新,比如Level,则不能采用这种方法。
  • 正确。另一个答案是相关的,但这种方法适用于更新所有字段(不包括键)。
  • 是的,这是完全错误的。如果我只想从新的冲突更新单列值会发生什么。在上述情况下,所有其他数据将被不正确的新数据替换。
【解决方案3】:

您需要在表上设置一个约束以触发“conflict”,然后通过替换来解决:

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 值。

【讨论】:

    【解决方案4】:

    我相信你想要UPSERT

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

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

    【讨论】:

      【解决方案5】:

      您应该使用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】:

      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(语法已添加到版本 3.24.0 (2018-06-04) 的 SQLite)

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

      excluded. 前缀等于 VALUES ('SQLite') 中的值。

      【讨论】:

        【解决方案7】:

        如果你没有主键,你可以插入,如果不存在,然后做一个更新。在使用此表之前,该表必须至少包含一个条目。

        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 列没有默认值。尽管这是一个冗长的解决方案,但它可以正常工作并按预期工作。
        【解决方案8】:

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

        例如,如果您只想在当前未使用 NAME 字段的情况下插入一条记录,并且如果是,您希望触发一个约束异常来告诉您,那么 INSERT OR REPLACE 不会抛出异常,而是会通过替换冲突记录(具有相同 NAME 的现有记录)来解决 UNIQUE 约束本身. Gaspard's 在上面的 his answer 中很好地证明了这一点。

        如果您想触发约束异常,您必须使用 INSERT 语句,并在知道名称后依靠单独的 UPDATE 命令更新记录没有被占用。

        【讨论】:

          【解决方案9】:

          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,但它对我不起作用
          • 您是否逐字尝试了以上两个查询?
          • 请注意,Ubuntu 18.04 附带 SQLite3 v3.22,而不是 3.25,因此它不支持 UPSERT 语法。
          • 感谢该功能的参考版本!
          猜你喜欢
          • 2012-08-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-15
          • 1970-01-01
          相关资源
          最近更新 更多