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列的新数据?


当前回答

伯恩哈特的最新消息:

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

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

其他回答

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

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

如果你通常做插入,我会

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

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

扩展亚里士多德的答案,你可以从一个虚拟的“单行”表(你自己创建的单行表)中选择。这避免了一些重复。

我还保留了示例在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";

遵循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

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语句中表示“如果有新内容,则插入新内容,否则保留旧内容”——例如,如果您正在使用固定查询并将新值与占位符绑定。

这里有一个解决方案,它实际上是UPSERT (UPDATE或INSERT),而不是INSERT或REPLACE(在许多情况下工作方式不同)。

它是这样工作的: 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;