ChatGPT解决这个技术问题 Extra ChatGPT

SQLite - UPSERT *not* INSERT 或 REPLACE

http://en.wikipedia.org/wiki/Upsert

Insert Update stored proc on SQL Server

在 SQLite 中是否有一些我没有想到的聪明方法?

基本上,如果记录存在,我想更新四列中的三列,如果不存在,我想用第四列的默认(NUL)值插入记录。

ID 是主键,因此 UPSERT 永远只有一条记录。

(我试图避免 SELECT 的开销以确定我是否需要 UPDATE 或 INSERT 显然)

建议?

我无法在 SQLite 站点上确认 TABLE CREATE 的语法。我还没有建立一个演示来测试它,但它似乎不受支持。

如果是,我有三列,所以它实际上看起来像:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

但前两个 blob 不会引起冲突,只有 ID 会所以我假设 Blob1 和 Blob2 不会被替换(根据需要)

当绑定数据是一个完整的事务时,SQLite 中的 UPDATE,这意味着要更新的每个发送的行都需要: Prepare/Bind/Step/Finalize 语句与允许使用重置功能的 INSERT 语句不同

语句对象的生命周期是这样的:

使用 sqlite3_prepare_v2() 创建对象 使用 sqlite3_bind_ 接口将值绑定到主机参数。通过调用 sqlite3_step() 运行 SQL 使用 sqlite3_reset() 重置语句,然后返回步骤 2 并重复。使用 sqlite3_finalize() 销毁语句对象。

UPDATE 我猜与 INSERT 相比速度很慢,但它与使用主键的 SELECT 相比如何?

也许我应该使用 select 来读取第 4 列(Blob3),然后使用 REPLACE 编写一条新记录,将原始第 4 列与前 3 列的新数据混合?

SQLite - 预发行版中可用的 UPSERT 参考:sqlite.1065341.n5.nabble.com/…
UPSERT 在 SQLite 3.24.0 版中可用

I
Iulian Onofrei

假设表中有三列:ID、NAME、ROLE

坏:这将使用 ID=1 的新值插入或替换所有列:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

坏:这将插入或替换 2 列... NAME 列将设置为 NULL 或默认值:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

GOOD:使用 SQLite On 冲突子句 UPSERT support in SQLite! UPSERT 语法已添加到版本 3.24.0 的 SQLite!

UPSERT 是对 INSERT 的一种特殊语法,如果 INSERT 违反唯一性约束,它会导致 INSERT 表现为 UPDATE 或无操作。 UPSERT 不是标准 SQL。 SQLite 中的 UPSERT 遵循 PostgreSQL 建立的语法。

https://i.stack.imgur.com/h475O.gif

好但乏味:这将更新 2 列。当 ID=1 存在时,NAME 将不受影响。当 ID=1 不存在时,名称将是默认值 (NULL)。

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

这将更新 2 列。当 ID=1 存在时,ROLE 不受影响。当 ID=1 不存在时,角色将被设置为 'Benchwarmer' 而不是默认值。

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

+1 精彩!如果您需要组合/比较任何字段的旧值和新值,嵌入的 select 子句使您可以灵活地覆盖默认的 ON CONFLICT REPLACE 功能。
如果Employee被级联删除的其他行引用,那么其他行仍然会被替换删除。
最后一个查询不正确。它应该是:coalesce((select role from Employee where id = 1),'Benchwarmer')
你能解释一下为什么这会插入或替换所有列的 ID=1 的新值:在你的第一个例子中被认为是坏的?您在此处显示的命令旨在创建一条 ID 为 1、名称为 John Foo 和角色 CEO 的新记录,或者使用该数据覆盖 ID 为 1 的记录(如果它已经存在)(假设 id 是主键) .那么,如果发生这种情况,为什么会很糟糕呢?
@Cornelius:很清楚,但这不是第一个示例中发生的情况。第一个示例旨在强制设置所有列,这正是发生的情况,无论是插入还是替换记录。那么,为什么这被认为是不好的呢?链接的答案还仅指出了为什么在指定列的子集时会发生不好的事情,例如在您的 second 示例中;它似乎没有详细说明在您的 first 示例 INSERT OR REPLACE 中发生的任何不良影响,同时为 all 列指定值。
h
hugomg

插入或替换不等同于“UPSERT”。

假设我有包含字段 id、name 和 role 的表 Employee:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

繁荣,您丢失了员工编号 1 的姓名。SQLite 已将其替换为默认值。

UPSERT 的预期输出是更改角色并保留名称。


但确实如此,在具有两列(且没有空值)的表上插入或替换等同于 UPSERT。
同意,它不是 100% upsert,但在某些情况下可以像上面一样喜欢它。所以程序员需要自己做出判断。
@QED 不,因为 delete + insert (这是一个替换)是 2 个 dml 语句,例如它们自己的触发器。它与仅 1 个更新语句不同。
C
Community

如果您只想保留现有行中的一列或两列,Eric B’s answer 是可以的。如果你想保留很多列,它会变得太麻烦。

这是一种可以很好地扩展到任意数量的列的方法。为了说明这一点,我将假设以下模式:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

特别注意 name 是行的自然键 - id 仅用于外键,因此 SQLite 在插入新行时需要自行选择 ID 值。但是当基于其 name 更新现有行时,我希望它继续具有旧的 ID 值(显然!)。

我使用以下构造实现了真正的 UPSERT

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

此查询的确切形式可能会有所不同。关键是将 INSERT SELECT 与左外连接一起使用,将现有行连接到新值。

在这里,如果之前不存在行,old.id 将是 NULL,然后 SQLite 将自动分配一个 ID,但如果已经存在这样的行,old.id 将具有实际值,并且将被重用。这正是我想要的。

事实上,这是非常灵活的。请注意 ts 列是如何在所有方面都完全丢失的——因为它有一个 DEFAULT 值,SQLite 在任何情况下都会做正确的事情,所以我不必自己照顾它。

您还可以在 newold 两侧都包含一列,然后在外部 SELECT 中使用例如 COALESCE(new.content, old.content) 表示“如果有任何内容,请插入新内容,否则保留旧内容” - 例如如果您使用的是固定查询并将新值与占位符绑定。


+1,效果很好,但在 SELECT ... AS old 上添加 WHERE name = "about" 约束以加快速度。如果你有 1m+ 行,这非常慢。
好点,对您的评论+1。不过,我将把它排除在答案之外,因为添加这样的 WHERE 子句只需要查询中的那种冗余,当我想出这种方法时,我一开始就试图避免这种冗余。一如既往:当你需要性能时,非规范化——在这种情况下是查询的结构。
如果您愿意,可以将亚里士多德的示例简化为:INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
这不会在执行替换(即更新)时不必要地触发 ON DELETE 触发器吗?
它肯定会触发 ON DELETE 触发器。不知道不必要的。对于大多数用户来说,这可能是不必要的,甚至是不需要的,但可能并非对所有用户都适用。同样,它还会级联删除任何带有外键的行到有问题的行中——这对许多用户来说可能是个问题。不幸的是,SQLite 没有更接近真正的 UPSERT。 (我猜,除了用 INSTEAD OF UPDATE 触发器伪造它。)
A
AnthonyLambert

此答案已更新,因此以下评论不再适用。

2018-05-18 停止新闻。

UPSERT support in SQLite! UPSERT 语法已添加到 SQLite 版本 3.24.0(待定)!

UPSERT 是对 INSERT 的一种特殊语法,如果 INSERT 违反唯一性约束,它会导致 INSERT 表现为 UPDATE 或无操作。 UPSERT 不是标准 SQL。 SQLite 中的 UPSERT 遵循 PostgreSQL 建立的语法。

https://i.stack.imgur.com/h475O.gif

或者:

另一种完全不同的方法是:在我的应用程序中,当我在内存中创建行时,我将内存中的 rowID 设置为 long.MaxValue。 (MaxValue 永远不会被用作 ID,您将活得不够长......然后,如果 rowID 不是那个值,那么它必须已经在数据库中,所以如果它是 MaxValue 则需要更新,那么它需要插入。仅当您可以跟踪应用程序中的 rowID 时,这才有用。


INSERT INTO table(...) SELECT ... WHERE changes() = 0; 适合我。
很好很简单,但是当行在更新和插入之间被删除时会出现竞争条件,不是吗?
@w00t 如果您在事务中运行它会怎样?
@copolii 实际上不确定这是否足够,也许它应该锁定该行?
这就是 nodejs sqlite-upsert 模块的工作方式。 github.com/pbrandt1/sqlite3-upsert/blob/master/index.js
S
Sam Saffron

如果您通常进行更新,我会..

开始一个事务 做更新 检查行数 如果是 0 做插入 提交

如果您通常进行插入,我会

开始事务尝试插入检查主键违规错误如果我们收到错误执行更新提交

这样你就可以避免选择,并且你在 Sqlite 上的事务是健全的。


如果您要在第三步使用 sqlite3_changes() 检查行数,请确保不要使用来自多个线程的数据库句柄进行修改。
以下内容会不会不那么罗嗦,但效果相同:1)select id form table where id = 'x' 2)if (ResultSet.rows.length == 0) update table where id = 'x';
C
Chris Stavropoulos

我意识到这是一个旧线程,但我最近一直在使用 sqlite3 并想出了这种方法,它更适合我动态生成参数化查询的需求:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

它仍然是 2 个查询,更新时带有 where 子句,但似乎可以解决问题。我也有这样的想法,如果对 changes() 的调用大于零,sqlite 可以完全优化更新语句。它是否真的这样做超出了我的知识范围,但一个人可以做梦,不是吗? ;)

对于奖励积分,您可以附加此行,它会返回该行的 id,无论它是新插入的行还是现有行。

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

L
Lukasz Szozda

从版本 3.24.0 开始,SQLite 支持 UPSERT。

documentation

UPSERT 是对 INSERT 的一种特殊语法,如果 INSERT 违反唯一性约束,它会导致 INSERT 表现为 UPDATE 或无操作。 UPSERT 不是标准 SQL。 SQLite 中的 UPSERT 遵循 PostgreSQL 建立的语法。 UPSERT 语法已添加到 SQLite 版本 3.24.0(待定)。 UPSERT 是一个普通的 INSERT 语句,后跟特殊的 ON CONFLICT 子句

https://www.sqlite.org/images/syntax/upsert-clause.gif

图片来源:https://www.sqlite.org/images/syntax/upsert-clause.gif

例子:

CREATE TABLE t1(id INT PRIMARY KEY, c TEXT);
INSERT INTO t1(id, c) VALUES (1,'a'), (2, 'b');
SELECT * FROM t1;


INSERT INTO t1(id, c) VALUES (1, 'c');
-- UNIQUE constraint failed: t1.id

INSERT INTO t1(id, c) VALUES (1, 'c')
ON CONFLICT DO NOTHING;

SELECT * FROM t1;

INSERT INTO t1(id, c)
VALUES (1, 'c')
ON CONFLICT(id) DO UPDATE SET c = excluded.c;

SELECT * FROM t1;

db<>fiddle demo


截至 API 27,Android 仍为 3.19
D
David Liebeherr

这是一个真正是 UPSERT(更新或插入)而不是插入或替换(在许多情况下工作方式不同)的解决方案。

它的工作原理如下:
1. 如果存在具有相同 Id 的记录,请尝试更新。
2. 如果更新没有更改任何行 (NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)),则插入记录。

因此,要么更新现有记录,要么执行插入。

重要的细节是使用 changes() SQL 函数检查更新语句是否命中任何现有记录,如果没有命中任何记录,则仅执行插入语句。

值得一提的是,changes() 函数不返回由较低级别的触发器执行的更改(请参阅 http://sqlite.org/lang_corefunc.html#changes),因此请务必考虑到这一点。

这是SQL...

测试更新:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

测试插入:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

这对我来说似乎是比 Eric 更好的解决方案。但是 INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0; 也应该可以工作。
B
Brill Pappin

伯恩哈特的更新:

你确实可以在 SQLite 中做一个 upsert,它只是看起来和你习惯的有点不同。它看起来像:

INSERT INTO table_name (id, column1, column2) 
VALUES ("youruuid", "value12", "value2")
ON CONFLICT(id) DO UPDATE 
SET column1 = "value1", column2 = "value2"

只是想向 android 程序员(也许也向其他人)指出“upsert”语法来自 2018 年(Sqlite 3.24)。因此,除非您使用最新的 API,否则您将无法使用此 API。
J
JosephStyons

我知道的最好的方法是先更新,然后再插入。 “选择的开销”是必要的,但这并不是一个可怕的负担,因为您正在搜索主键,这很快。

您应该能够使用您的表和字段名称修改以下语句以执行您想要的操作。

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

我认为这不是一个好主意,因为您需要对数据库引擎进行两次请求。
C
Community

Aristotle’s answer 上展开,您可以从一个虚拟的“单例”表(您自己创建的单行表)中进行 SELECT。这避免了一些重复。

我还使示例在 MySQL 和 SQLite 之间具有可移植性,并使用“date_added”列作为示例,说明如何仅在第一次设置列。

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

Z
Zappescu

如果有人想阅读我在 Cordova 中的 SQLite 解决方案,由于上面的@david 回答,我得到了这个通用的 js 方法。

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

因此,首先使用此函数获取列名:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

然后以编程方式构建事务。

“值”是您应该在之前构建的数组,它代表您要插入或更新到表中的行。

“remoteid”是我用作参考的 id,因为我正在与远程服务器同步。

SQLite Cordova插件的使用请参考官方的link


k
klozovin

我认为这可能是您正在寻找的:ON CONFLICT clause

如果你这样定义你的表:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

现在,如果您使用已经存在的 id 执行 INSERT,SQLite 会自动执行 UPDATE 而不是 INSERT。

嗯...


我认为这行不通,它会清除插入语句中缺少的列
@Mosor:我的-1,对不起。这与发出 REPLACE 语句相同。
-1 因为如果主键已经存在,这会先删除然后再插入。
D
Dodzi Dzakuma

此方法重新混合了此问题答案中的一些其他方法,并结合了 CTE(通用表表达式)的使用。我将介绍查询,然后解释我为什么这样做。

如果有员工 300,我想将员工 300 的姓氏更改为 DAVIS。否则,我将添加一个新员工。

表名:员工列:id、first_name、last_name

查询是:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

基本上,我使用 CTE 来减少必须使用 select 语句来确定默认值的次数。由于这是一个 CTE,我们只需从表中选择我们想要的列,INSERT 语句就会使用它。

现在,您可以通过在 COALESCE 函数中将空值替换为应该使用的值来决定要使用的默认值。


M
Miquel

遵循 Aristotle PagaltzisEric B’s answer 中的 COALESCE 的想法,这里是一个 upsert 选项,仅更新少数列或插入整行(如果不存在)。

在这种情况下,假设应该更新标题和内容,在存在时保留其他旧值并在找不到名称时插入提供的值:

注意 idINSERT 时被强制为 NULL,因为它应该是自动增量的。如果它只是一个生成的主键,那么也可以使用 COALESCE(参见 Aristotle Pagaltzis comment)。

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

所以一般规则是,如果你想保留旧值,使用 COALESCE,当你想更新值时,使用 new.fieldname


COALESCE(old.id, new.id) 使用自动递增键绝对是错误的。虽然“保持大部分行不变,除了缺少值的地方”听起来像是某人实际上可能拥有的用例,但我认为人们在寻找如何进行 UPSERT 时并不是要寻找的。
@AristotlePagaltzis 道歉,如果我错了,我没有使用自动增量。我通过这个查询寻找的是只更新几个列或插入带有提供值的整行,以防它不存在。我一直在使用您的查询,但在插入时无法实现:从分配给 NULLold 表中选择的列,而不是 new 中提供的值。这就是使用 COALESCE 的原因。我不是 sqlite 方面的专家,我一直在测试这个查询并且似乎适用于这种情况,如果你能指出我的自动增量解决方案,我将非常感谢
如果是自动递增键,您想要插入 NULL 作为键,因为这会告诉 SQLite 插入下一个可用值。
我并不是说你不应该做你正在做的事情(如果你需要它,那么你就需要它),只是这并不是这里问题的真正答案。 UPSERT 通常意味着您有一行要存储在表中,只是不知道您是否已经有匹配的行可以将值放入或需要将它们作为新行插入。您的用例是,如果您已经有匹配的行,您希望忽略新行中的大多数值。没关系,这不是被问到的问题。
s
sapbucket

如果您不介意在两个操作中执行此操作。

脚步:

1)使用“插入或忽略”添加新项目

2) 使用“UPDATE”更新现有项目

这两个步骤的输入是相同的新项目或可更新项目的集合。适用于无需更改的现有项目。它们将被更新,但使用相同的数据,因此最终结果是没有变化。

当然,速度较慢等。效率低下。是的。

写sql容易维护和理解吗?确实。

这是一个需要考虑的权衡。非常适合小型 upserts。非常适合那些不介意为了代码可维护性而牺牲效率的人。


大家注意:插入或替换不是这篇文章的内容。 INSERT OR REPLACE 将在您的表中创建一个带有新 ID 的新行。那不是更新。
B
BSalita

使用 WHERE 选择更新日期记录的完整示例。

-- https://www.db-fiddle.com/f/7jyj4n76MZHLLk2yszB6XD/22
 
DROP TABLE IF EXISTS db;

CREATE TABLE db
(
 id PRIMARY KEY,
 updated_at,
 other
);

-- initial INSERT
INSERT INTO db (id,updated_at,other) VALUES(1,1,1);

SELECT * FROM db;

-- INSERT without WHERE
INSERT INTO db (id,updated_at,other) VALUES(1,2,2)
ON CONFLICT(id) DO UPDATE SET updated_at=excluded.updated_at;

SELECT * FROM db;

-- WHERE is FALSE
INSERT INTO db (id,updated_at,other) VALUES(1,2,3)
ON CONFLICT(id) DO UPDATE SET updated_at=excluded.updated_at, other=excluded.other
WHERE excluded.updated_at > updated_at;

SELECT * FROM db;

-- ok to SET a PRIMARY KEY. WHERE is TRUE
INSERT INTO db (id,updated_at,other) VALUES(1,3,4)
ON CONFLICT(id) DO UPDATE SET id=excluded.id, updated_at=excluded.updated_at, other=excluded.other
WHERE excluded.updated_at > updated_at;

SELECT * FROM db;

c
cwallenpoole

刚刚阅读了这个帖子,并对这个“UPSERT”并不容易感到失望,我进一步调查了......

实际上,您可以在 SQLITE 中直接轻松地执行此操作。

而不是使用:INSERT INTO

使用:INSERT OR REPLACE INTO

这正是你想要它做的!


-1 INSERT OR REPLACE 不是 UPSERT。有关原因,请参见 gregschlom's "answer"Eric B's solution 确实有效,需要一些支持。
m
mjb
SELECT COUNT(*) FROM table1 WHERE id = 1;

如果COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

否则如果 COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

这太复杂了,SQL 可以在一个查询中处理得很好