http://en.wikipedia.org/wiki/Upsert
Insert Update stored proc on SQL Server
在SQLite中是否有一些我没有想到的聪明方法?
基本上,如果记录存在,我想更新四列中的三列, 如果它不存在,我想用第四列的默认(NUL)值插入记录。
ID是主键,因此UPSERT只会有一条记录。
(我试图避免SELECT的开销,以便确定我是否需要更新或插入显然)
建议?
我无法在SQLite网站上确认SQL 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会 所以我asusme Blob1和Blob2不会被替换(根据需要)
绑定数据时SQLite中的UPDATE是一个完整的事务,意思是 每个要更新的发送行都需要:准备/绑定/步骤/完成语句 不像INSERT允许使用重置功能
语句对象的生命周期如下:
更新我猜测与INSERT相比很慢,但是如何使用主键与SELECT相比呢?
也许我应该使用select来读取第4列(Blob3),然后使用REPLACE编写一条新记录,将原始的第4列与前3列的新数据混合在一起?
答案 0 :(得分:810)
假设表中有3列.. ID,NAME,ROLE
BAD:这将使用ID = 1的新值插入或替换所有列:
INSERT OR REPLACE INTO Employee (id, name, role)
VALUES (1, 'John Foo', 'CEO');
BAD:这将插入或替换2列... NAME列将设置为NULL或默认值:
INSERT OR REPLACE INTO Employee (id, role)
VALUES (1, 'code monkey');
好:这将更新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 :(得分:125)
INSERT OR REPLACE NOT 等同于“UPSERT”。
假设我有表Employee,其中包含字段id,name和role:
INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")
Boom,您丢失了员工编号1的名称.SQLite已将其替换为默认值。
UPSERT的预期输出是改变角色并保留名称。
答案 2 :(得分:106)
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在任何情况下都会做正确的事情,因此我不必自己处理它
您还可以在new
和old
两侧添加一列,然后使用例如外部COALESCE(new.content, old.content)
中的SELECT
说“如果有新内容,请插入新内容,否则请保留旧内容” - 例如如果您使用固定查询并将新值与占位符绑定。
答案 3 :(得分:78)
如果你一般都在做更新我会......
如果你一般都在做插页,我会
通过这种方式,您可以避免选择,并且您在Sqlite上具有事务性。
答案 4 :(得分:66)
此答案已更新,因此以下评论不再适用。
2018-05-18 STOP PRESS。
UPSERT support in SQLite! UPSERT语法已添加到SQLite版本3.24.0(待定)!
UPSERT是INSERT的一种特殊语法添加,如果INSERT违反唯一性约束,则会导致INSERT表现为UPDATE或no-op。 UPSERT不是标准SQL。 SQLite中的UPSERT遵循PostgreSQL建立的语法。
或者:
另一种完全不同的方法是:在我的应用程序中,当我在内存中创建行时,我将内存rowID设置为long.MaxValue。 (MaxValue将永远不会被用作ID,你将不会活得足够长....然后如果rowID不是那个值,那么它必须已经在数据库中,所以如果它是MaxValue需要更新,那么它需要插入。只有在您可以跟踪应用中的rowID时,此功能才有用。
答案 5 :(得分:59)
我意识到这是一个旧线程,但我最近一直在使用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;
答案 6 :(得分:13)
这是一个真正的UPSERT(UPDATE或INSERT)而不是INSERT OR REPLACE(在许多情况下工作方式不同)的解决方案。
它的工作原理如下:
1.如果存在具有相同Id的记录,请尝试更新
2.如果更新未更改任何行(NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
),则插入记录。
因此,现有记录已更新或将执行插入。
重要的细节是使用changes()SQL函数来检查update语句是否命中了任何现有记录,如果没有命中任何记录,则只执行insert语句。
有一点需要注意的是,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;
答案 7 :(得分:5)
扩展Aristotle’s answer你可以从一个虚拟的'singleton'表中选择一个表(你自己创建一个表的一行表)。这避免了一些重复。
我还让这个示例可以在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";
答案 8 :(得分:5)
您确实可以在SQLite中进行upsert,它看起来与您以前的习惯略有不同。看起来像这样:
INSERT INTO table name (column1, column2)
VALUES ("value12", "value2") WHERE id = 123
ON CONFLICT DO UPDATE
SET column1 = "value1", column2 = "value2" WHERE id = 123
答案 9 :(得分:4)
UPSERT是INSERT的一种特殊语法添加,如果INSERT违反唯一性约束,则会导致INSERT表现为UPDATE或no-op。 UPSERT不是标准SQL。 SQLite中的UPSERT遵循PostgreSQL建立的语法。 UPSERT语法被添加到SQLite版本3.24.0(待定)。
UPSERT是一个普通的INSERT语句,后跟特殊的ON CONFLICT子句
答案 10 :(得分:3)
我知道的最佳方法是进行更新,然后进行插入。 “选择的开销”是必要的,但是由于您在主键上搜索速度很快,因此这不是一个可怕的负担。
您应该可以使用您的表格修改以下对帐单&amp;字段名称可以做你想要的。
--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
);
答案 11 :(得分:3)
如果有人想在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);
});
});
}
然后以编程方式构建事务。
“Values”是您之前应构建的数组,它表示您要插入或更新到表中的行。
“remoteid”是我用作参考的ID,因为我正在与远程服务器同步。
有关使用SQLite Cordova插件的信息,请参阅官方link
答案 12 :(得分:1)
我认为这可能就是您所寻找的:ON CONFLICT clause。
如果你这样定义你的表:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
field1 TEXT
);
现在,如果你使用已经存在的id进行INSERT,SQLite会自动执行UPDATE而不是INSERT。
... H个
答案 13 :(得分:1)
如果您不介意两次操作。
步骤:
1)使用“ INSERT OR IGNORE”添加新项目
2)使用“ UPDATE”更新现有项目
两个步骤的输入是相同的新项目或可更新项目的集合。适用于不需要更改的现有项目。它们将被更新,但是具有相同的数据,因此最终结果将保持不变。
当然,速度较慢等。效率低下。是的
易于编写sql并维护和理解它吗?绝对是
要权衡考虑。 非常适合小型upsert。对于那些不介意牺牲代码可维护性效率的人来说非常有用。
答案 14 :(得分:1)
关注Aristotle Pagaltzis以及来自Eric B’s answer的COALESCE
的想法,这里是一个upsert选项,只更新几列或插入完整行(如果它不存在)。
在这种情况下,假设标题和内容应该更新,保留现有的其他旧值,并在找不到名称时插入提供的值:
注意 id
在INSERT
时被强制为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
答案 15 :(得分:1)
这个方法重新混合了一些其他方法来回答这个问题并结合使用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函数中的空值替换为值,来决定要使用的默认值。
答案 16 :(得分:0)
使用 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;
答案 17 :(得分:-2)
刚刚阅读了这个帖子并且对于这次“UPSERT”并不容易感到失望,我进一步调查了......
您可以在SQLITE中直接轻松地执行此操作。
而不是使用:INSERT INTO
使用:INSERT OR REPLACE INTO
这正是你想要它做的!
答案 18 :(得分:-4)
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;