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

在SQL Server上插入更新存储过程

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

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

ID是一个主键,所以UPSERT只会有一条记录。

(我试图避免选择的开销,以确定我是否需要更新或插入明显)

建议吗?


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

如果是的话,我有三列,所以它实际上是这样的:

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

但前两个斑点不会引起冲突,只有ID会 所以我假设Blob1和Blob2不会被替换(如期望的那样)


SQLite中绑定数据时的更新是一个完整的事务,也就是说 每个要更新的发送行都需要:Prepare/Bind/Step/Finalize语句 与允许使用重置函数的INSERT不同

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

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

更新我猜是慢与插入相比,但它如何比较选择使用主键?

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


当前回答

Eric B的答案是可以的,如果你只想从现有的行中保留一到两列。如果您想保留很多列,那么它会变得非常麻烦。

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

 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值本身。但是,当根据现有行的名称更新该行时,我希望它继续拥有旧的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列是如何完全缺失的-因为它有一个默认值,SQLite在任何情况下都会做正确的事情,所以我不必自己照顾它。

你也可以在新侧和旧侧同时包含一个列,然后使用例如COALESCE(new。content, old.content)在外层的SELECT语句中表示“如果有新内容,则插入新内容,否则保留旧内容”——例如,如果您正在使用固定查询并将新值与占位符绑定。

其他回答

遵循Aristotle Pagaltzis和Eric B回答中的COALESCE思想,这里是一个upsert选项,用于仅更新少数列或在不存在时插入整行。

在这种情况下,想象一下标题和内容应该被更新,当存在时保持其他旧值,当没有找到name时插入提供的值:

说明id在INSERT时被强制为NULL,因为它应该是自动递增的。如果它只是一个生成的主键,那么也可以使用COALESCE(参见Aristotle Pagaltzis的注释)。

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

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

从文档中可以看到:

UPSERT是INSERT的一个特殊语法,如果INSERT违反唯一性约束,它会导致INSERT行为为UPDATE或no-op。UPSERT不是标准SQL。SQLite中的UPSERT遵循PostgreSQL建立的语法。UPSERT语法被添加到3.24.0版本的SQLite中(待定)。 UPSERT是一个普通的INSERT语句,后面跟着特殊的ON冲突子句

图片来源: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演示

SELECT COUNT(*) FROM table1 WHERE id = 1;

如果COUNT(*) = 0

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

else if COUNT(*) > 0

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

这个答案已经更新了,所以下面的评论不再适用。

2018-05-18停止按。

在SQLite中支持UPSERT !UPSERT语法已在3.24.0版本添加到SQLite(待定)!

UPSERT是INSERT的一个特殊语法,如果INSERT违反唯一性约束,它会导致INSERT行为为UPDATE或no-op。UPSERT不是标准SQL。SQLite中的UPSERT遵循PostgreSQL建立的语法。

另外:

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

如果你通常做更新,我会..

开始事务 进行更新 检查行数 如果是0,则插入 提交

如果你通常做插入,我会

开始事务 尝试插入 检查主键违反错误 如果出现错误,就进行更新 提交

这样你就避免了选择,你在Sqlite上的事务是健全的。