UPSERT *不是* INSERT 或 REPLACE
http://en.wikipedia.org/wiki/Upsert
有没有一些我没有想到的聪明的方法可以在 SQLite 中执行此操作的?
基本上,如果记录存在,我想更新四列中的三列。 如果它不存在,我想插入第四列具有默认(NULL)值的记录。
ID 是主键,因此只有一条记录需要 UPSERT。
(我试图避免 SELECT 的开销,以确定是否需要更新或插入显然)
建议?
我无法在 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中的更新,这意味着 要更新的每个发送行都需要: 与允许使用重置功能的 INSERT 不同,Prepare/Bind/Step/Finalize 语句
语句对象的生命周期如下:
- 使用 sqlite3_prepare_v2() 创建对象
- 将值绑定到主机参数使用 sqlite3_bind_ 接口。
- 通过调用 sqlite3_step() 运行 SQL
- 使用 sqlite3_reset() 重置语句,然后返回到步骤 2 并重复。
- 使用 sqlite3_finalize() 销毁语句对象。
我猜 UPDATE 比 INSERT 慢,但是它与使用主键的 SELECT 相比如何?
也许我应该使用 select 读取第 4 列 (Blob3),然后使用 REPLACE 写入一条新记录,将原始第 4 列与前 3 列的新数据混合?
http://en.wikipedia.org/wiki/Upsert
Insert Update stored proc on SQL Server
Is there some clever way to do this in SQLite that I have not thought of?
Basically I want to update three out of four columns if the record exists. If it does not exist, I want to INSERT the record with the default (NULL) value for the fourth column.
The ID is a primary key, so there will only ever be one record to UPSERT.
(I am trying to avoid the overhead of SELECT in order to determine if I need to UPDATE or INSERT obviously)
Suggestions?
I cannot confirm that syntax on the SQLite site for TABLE CREATE.
I have not built a demo to test it, but it doesn't seem to be supported.
If it was, I have three columns so it would actually look like:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
but the first two blobs will not cause a conflict, only the ID would
So I assume Blob1 and Blob2 would not be replaced (as desired)
UPDATEs in SQLite when binding data are a complete transaction, meaning
Each sent row to be updated requires: Prepare/Bind/Step/Finalize statements unlike the INSERT which allows the use of the reset function
The life of a statement object goes something like this:
- Create the object using sqlite3_prepare_v2()
- Bind values to host parameters using sqlite3_bind_ interfaces.
- Run the SQL by calling sqlite3_step()
- Reset the statement using sqlite3_reset() then go back to step 2 and repeat.
- Destroy the statement object using sqlite3_finalize().
UPDATE I am guessing is slow compared to INSERT, but how does it compare to SELECT using the Primary key?
Perhaps I should use the select to read the 4th column (Blob3) and then use REPLACE to write a new record blending the original 4th Column with the new data for the first 3 columns?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
假设表中有三列:ID、NAME、ROLE
BAD: 这将插入或替换所有列为 ID=1 的新值:
BAD: 这将插入或替换2 列...NAME 列将设置为 NULL 或默认值:
GOOD:使用 SQLite On 冲突子句
SQLite 中的 UPSERT 支持! UPSERT 语法已添加到 SQLite 3.24.0 版本中!
UPSERT 是 INSERT 的一种特殊语法补充,如果 INSERT 违反唯一性约束,则该语法会导致 INSERT 表现为 UPDATE 或无操作。 UPSERT 不是标准 SQL。 SQLite 中的 UPSERT 遵循 PostgreSQL 建立的语法。
很好但乏味:这将更新其中 2 列。
当ID=1存在时,NAME不受影响。
当ID=1不存在时,名称将为默认值(NULL)。
这将更新其中 2 列。
当ID=1存在时,ROLE不受影响。
当ID=1不存在时,角色将被设置为“Benchwarmer”而不是默认值。
Assuming three columns in the table: ID, NAME, ROLE
BAD: This will insert or replace all columns with new values for ID=1:
BAD: This will insert or replace 2 of the columns... the NAME column will be set to NULL or the default value:
GOOD: Use SQLite On conflict clause
UPSERT support in SQLite! UPSERT syntax was added to SQLite with version 3.24.0!
UPSERT is a special syntax addition to INSERT that causes the INSERT to behave as an UPDATE or a no-op if the INSERT would violate a uniqueness constraint. UPSERT is not standard SQL. UPSERT in SQLite follows the syntax established by PostgreSQL.
GOOD but tedious: This will update 2 of the columns.
When ID=1 exists, the NAME will be unaffected.
When ID=1 does not exist, the name will be the default (NULL).
This will update 2 of the columns.
When ID=1 exists, the ROLE will be unaffected.
When ID=1 does not exist, the role will be set to 'Benchwarmer' instead of the default value.
INSERT OR REPLACE不等同于“UPSERT”。
假设我有一张 Employee 表,其中包含字段 id、name 和 role:
Boom,您丢失了员工编号 1 的姓名。SQLite 已将其替换为默认值。
UPSERT 的预期输出是更改角色并保留名称。
INSERT OR REPLACE is NOT equivalent to "UPSERT".
Say I have the table Employee with the fields id, name, and role:
Boom, you've lost the name of the employee number 1. SQLite has replaced it with a default value.
The expected output of an UPSERT would be to change the role and to keep the name.
如果您只想保留现有行中的一列或两列,Eric B 的答案是可以的。 如果你想保留很多列,它很快就会变得太麻烦。
这是一种可以很好地扩展到任意数量的列的方法。 为了说明这一点,我将假设以下模式:
特别注意
name
是行的自然键 -id
仅用于外键,所以重点是SQLite 在插入新行时选择 ID 值本身。 但是,当根据其名称
更新现有行时,我希望它继续具有旧的 ID 值(显然!)。我使用以下构造实现了真正的
UPSERT
:该查询的确切形式可能略有不同。 关键是使用带有左外连接的 INSERT SELECT 来将现有行连接到新值。
在这里,如果一行以前不存在,
old.id
将为NULL
,然后SQLite将自动分配一个ID,但如果已经存在这样的行,old.id
将具有实际值,并且该值将被重用。 这正是我想要的。事实上,这是非常灵活的。 请注意 ts 列在所有方面都完全缺失 - 因为它有一个 DEFAULT 值,SQLite 在任何情况下都会做正确的事情,所以我没有我自己来照顾它。
您还可以在
new
和old
两侧都包含一列,然后在COALESCE(new.content, old.content)
中使用外部SELECT
表示“如果有新内容,则插入新内容,否则保留旧内容”——例如,如果您使用固定查询并将新值与占位符绑定。Eric B’s answer is OK if you want to preserve just one or maybe two columns from the existing row. If you want to preserve a lot of columns, it gets too cumbersome fast.
Here’s an approach that will scale well to any amount of columns on either side. To illustrate it I will assume the following schema:
Note in particular that
name
is the natural key of the row –id
is used only for foreign keys, so the point is for SQLite to pick the ID value itself when inserting a new row. But when updating an existing row based on itsname
, I want it to continue to have the old ID value (obviously!).I achieve a true
UPSERT
with the following construct:The exact form of this query can vary a bit. The key is the use of
INSERT SELECT
with a left outer join, to join an existing row to the new values.Here, if a row did not previously exist,
old.id
will beNULL
and SQLite will then assign an ID automatically, but if there already was such a row,old.id
will have an actual value and this will be reused. Which is exactly what I wanted.In fact this is very flexible. Note how the
ts
column is completely missing on all sides – because it has aDEFAULT
value, SQLite will just do the right thing in any case, so I don’t have to take care of it myself.You can also include a column on both the
new
andold
sides and then use e.g.COALESCE(new.content, old.content)
in the outerSELECT
to say “insert the new content if there was any, otherwise keep the old content” – e.g. if you are using a fixed query and are binding the new values with placeholders.此答案已更新,因此以下评论不再适用。
2018-05-18停止按。
SQLite 中的 UPSERT 支持! UPSERT 语法已添加到 SQLite 3.24.0 版本(待定)!
UPSERT 是 INSERT 的一种特殊语法补充,如果 INSERT 违反唯一性约束,则该语法会导致 INSERT 表现为 UPDATE 或无操作。 UPSERT 不是标准 SQL。 SQLite 中的 UPSERT 遵循 PostgreSQL 建立的语法。
或者:
另一种完全不同的方法:在我的应用程序中,当我在内存中创建行时,我将内存中的 rowID 设置为 long.MaxValue 。 (MaxValue 永远不会被用作 ID,你活得不够长......)然后,如果 rowID 不是该值,那么它必须已经在数据库中,因此需要更新,如果它是 MaxValue,则需要插入。 仅当您可以跟踪应用程序中的 rowID 时,这才有用。
This answer has been updated and so the comments below no longer apply.
2018-05-18 STOP PRESS.
UPSERT support in SQLite! UPSERT syntax was added to SQLite with version 3.24.0 (pending) !
UPSERT is a special syntax addition to INSERT that causes the INSERT to behave as an UPDATE or a no-op if the INSERT would violate a uniqueness constraint. UPSERT is not standard SQL. UPSERT in SQLite follows the syntax established by PostgreSQL.
alternatively:
Another completely different way of doing this: in my application I set my in memory rowID to be long.MaxValue when I create the row in memory. (MaxValue will never be used as an ID you won't live long enough....) Then if rowID is not that value then it must already be in the database so needs an UPDATE if it is MaxValue then it needs an insert. This is only useful if you can track the rowIDs in your app.
如果您通常进行更新,我会..
如果您通常进行插入,我会
这样您就可以避免选择并且在 Sqlite 上事务性良好。
If you are generally doing updates I would ..
If you are generally doing inserts I would
This way you avoid the select and you are transactionally sound on Sqlite.
我意识到这是一个旧线程,但我最近一直在 sqlite3 中工作,并想出了这种方法,它更适合我动态生成参数化查询的需求:
它仍然是 2 个带有更新的 where 子句的查询,但似乎确实如此窍门。 我的脑海中也有这样的愿景:如果对changes()的调用大于零,sqlite可以完全优化掉更新语句。 我不知道它是否真的如此,但人可以做梦,不是吗? ;)
对于奖励积分,您可以附加此行,它会返回行的 ID,无论它是新插入的行还是现有行。
I realize this is an old thread but I've been working in sqlite3 as of late and came up with this method which better suited my needs of dynamically generating parameterized queries:
It's still 2 queries with a where clause on the update but seems to do the trick. I also have this vision in my head that sqlite can optimize away the update statement entirely if the call to changes() is greater than zero. Whether or not it actually does that is beyond my knowledge, but a man can dream can't he? ;)
For bonus points you can append this line which returns you the id of the row whether it be a newly inserted row or an existing row.
从版本 3.24.0 开始,SQLite 支持 UPSERT。
来自文档:
图像来源:https://www.sqlite.org/images/syntax/upsert-clause.gif
示例:
db<>fiddle 演示
Beginning with version 3.24.0 UPSERT is supported by SQLite.
From the documentation:
Image source: https://www.sqlite.org/images/syntax/upsert-clause.gif
Example:
db<>fiddle demo
Bernhardt 的更新:
您确实可以在 SQLite 中执行更新插入,只是看起来与您习惯的有点不同。 它看起来像:
Updates from Bernhardt:
You can indeed do an upsert in SQLite, it just looks a little different than you are used to. It would look something like:
这是一个实际上是 UPSERT(更新或插入)而不是插入或替换(在许多情况下工作方式不同)的解决方案。
它的工作原理如下:
1. 如果存在相同Id的记录则尝试更新。
2. 如果更新未更改任何行 (
NOT EXISTS(SELECTchanges() ASchangeFROMContactWHEREchange<>0)
),则插入记录。因此,要么更新现有记录,要么执行插入。
重要的细节是使用changes() SQL函数来检查更新语句是否命中任何现有记录,并且仅在没有命中任何记录时才执行插入语句。
需要提到的一件事是,changes() 函数不会返回由较低级别触发器执行的更改(请参阅 http: //sqlite.org/lang_corefunc.html#changes),所以一定要考虑到这一点。
这是 SQL...
测试更新:
测试插入:
Here is a solution that really is an UPSERT (UPDATE or INSERT) instead of an INSERT OR REPLACE (which works differently in many situations).
It works like this:
1. Try to update if a record with the same Id exists.
2. If the update did not change any rows (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), then insert the record.So either an existing record was updated or an insert will be performed.
The important detail is to use the changes() SQL function to check if the update statement hit any existing records and only perform the insert statement if it did not hit any record.
One thing to mention is that the changes() function does not return changes performed by lower-level triggers (see http://sqlite.org/lang_corefunc.html#changes), so be sure to take that into account.
Here is the SQL...
Test update:
Test insert:
我所知道的最好的方法是先进行更新,然后进行插入。
“选择的开销”是必要的,但这并不是一个可怕的负担,因为您正在主键上搜索,速度很快。
您应该能够使用您的表和修改以下语句: 字段名称来执行您想要的操作。
The best approach I know is to do an update, followed by an insert.
The "overhead of a select" is necessary, but it is not a terrible burden since you are searching on the primary key, which is fast.
You should be able to modify the below statements with your table & field names to do what you want.
扩展亚里士多德的答案,您可以从虚拟“单例”表(您自己创建的单行表)中进行选择。 这避免了一些重复。
我还使该示例在 MySQL 和 SQLite 之间可移植,并使用“date_added”列作为如何仅在第一次设置列的示例。
Expanding on Aristotle’s answer you can SELECT from a dummy 'singleton' table (a table of your own creation with a single row). This avoids some duplication.
I've also kept the example portable across MySQL and SQLite and used a 'date_added' column as an example of how you could set a column only the first time.
如果有人想阅读我在 Cordova 中的 SQLite 解决方案,感谢上面的@david 回答,我得到了这个通用的 js 方法。
因此,首先使用此函数获取列名称:
然后以编程方式构建事务。
“Values”是您之前应该构建的数组,它代表您想要插入或更新到表中的行。
“remoteid”是我用作参考的 ID,因为我正在与远程服务器同步。
SQLite Cordova插件的使用请参考官方链接
If someone wants to read my solution for SQLite in Cordova, I got this generic js method thanks to @david answer above.
So, first pick up the column names with this function:
Then build the transactions programmatically.
"Values" is an array you should build before and it represents the rows you want to insert or update into the table.
"remoteid" is the id I used as a reference, since I'm syncing with my remote server.
For the use of the SQLite Cordova plugin, please refer to the official link
遵循亚里士多德Pagaltzis以及来自Eric B 的回答,这里是一个更新插入选项,用于仅更新几列或插入整行(如果不存在)。
在这种情况下,假设应该更新标题和内容,在存在时保留其他旧值,并在找不到名称时插入提供的值:
NOTE
id
强制为 NULL当INSERT
时,因为它应该是自动增量。 如果它只是生成的主键,则也可以使用COALESCE
(请参阅亚里士多德·帕加尔茨评论)。因此,一般规则是,如果您想保留旧值,请使用
COALESCE
,当您想更新值时,请使用new.fieldname
Following Aristotle Pagaltzis and the idea of
COALESCE
from Eric B’s answer, here it is an upsert option to update only few columns or insert full row if it does not exist.In this case, imagine that title and content should be updated, keeping the other old values when existing and inserting supplied ones when name not found:
NOTE
id
is forced to be NULL whenINSERT
as it is supposed to be autoincrement. If it is just a generated primary key thenCOALESCE
can also be used (see Aristotle Pagaltzis comment).So the general rule would be, if you want to keep old values, use
COALESCE
, when you want to update values, usenew.fieldname
我想这可能就是您正在寻找的:ON CONFLICT 条款。
如果你像这样定义你的表:
现在,如果你使用已经存在的 id 执行 INSERT,SQLite 会自动执行 UPDATE 而不是 INSERT。
呃……
I think this may be what you are looking for: ON CONFLICT clause.
If you define your table like this:
Now, if you do an INSERT with an id that already exists, SQLite automagically does UPDATE instead of INSERT.
Hth...
此方法重新混合了该问题答案中的一些其他方法,并结合了 CTE(通用表表达式)的使用。 我将介绍该查询,然后解释为什么我这样做。
如果有员工 300,我想将员工 300 的姓氏更改为 DAVIS。否则,我将添加一名新员工。
表名称:员工
列:id、first_name、last_name
查询是:
基本上,我使用 CTE 来减少必须使用 select 语句来确定默认值的次数。 由于这是一个 CTE,我们只需从表中选择我们想要的列,INSERT 语句就会使用它。
现在,您可以通过将 COALESCE 函数中的空值替换为应有的值来决定要使用的默认值。
This method remixes a few of the other methods from answer in for this question and incorporates the use of CTE (Common Table Expressions). I will introduce the query then explain why I did what I did.
I would like to change the last name for employee 300 to DAVIS if there is an employee 300. Otherwise, I will add a new employee.
Table Name: employees
Columns: id, first_name, last_name
The query is:
Basically, I used the CTE to reduce the number of times the select statement has to be used to determine default values. Since this is a CTE, we just select the columns we want from the table and the INSERT statement uses this.
Now you can decide what defaults you want to use by replacing the nulls, in the COALESCE function with what the values should be.
如果您不介意分两次执行此操作。
步骤:
1) 使用“INSERT OR IGNORE”添加新项目
2) 使用“UPDATE”更新现有项目 这
两个步骤的输入都是新项目或可更新项目的同一集合。 适用于无需更改的现有项目。 它们将被更新,但数据相同,因此最终结果没有变化。
当然,速度较慢等等,效率低下。 是的。
sql写起来容易维护和理解吗? 确实。
这是一个需要考虑的权衡。
非常适合小型更新。 对于那些不介意为了代码可维护性而牺牲效率的人来说非常有用。
If you don't mind doing this in two operations.
Steps:
1) Add new items with "INSERT OR IGNORE"
2) Update existing items with "UPDATE"
The input to both steps is the same collection of new or update-able items. Works fine with existing items that need no changes. They will be updated, but with the same data and therefore net result is no changes.
Sure, slower, etc. Inefficient. Yep.
Easy to write the sql and maintain and understand it? Definitely.
It's a trade-off to consider.
Works great for small upserts. Works great for those that don't mind sacrificing efficiency for code maintainability.
使用 WHERE 选择更新的日期记录的更新插入的完整示例。
Complete example of upserting using WHERE to select the newer dated record.
刚刚读完这篇文章,并对这个“UPSERT”操作并不容易感到失望,我进一步调查了......
实际上,您可以在 SQLITE 中直接轻松地完成此操作。
而不是使用:
INSERT INTO
使用:
INSERT OR REPLACE INTO
这正是您想要的!
Having just read this thread and been disappointed that it wasn't easy to just to this "UPSERT"ing, I investigated further...
You can actually do this directly and easily in SQLITE.
Instead of using:
INSERT INTO
Use:
INSERT OR REPLACE INTO
This does exactly what you want it to do!
如果
COUNT(*) = 0
否则如果
COUNT(*) > 0
if
COUNT(*) = 0
else if
COUNT(*) > 0