UPSERT *不是* INSERT 或 REPLACE

发布于 2024-07-11 20:58:27 字数 1283 浏览 12 评论 0原文

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

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

有没有一些我没有想到的聪明的方法可以在 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 语句

语句对象的生命周期如下:

  1. 使用 sqlite3_prepare_v2() 创建对象
  2. 将值绑定到主机参数使用 sqlite3_bind_ 接口。
  3. 通过调用 sqlite3_step() 运行 SQL
  4. 使用 sqlite3_reset() 重置语句,然后返回到步骤 2 并重复。
  5. 使用 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:

  1. Create the object using sqlite3_prepare_v2()
  2. Bind values to host parameters using sqlite3_bind_ interfaces.
  3. Run the SQL by calling sqlite3_step()
  4. Reset the statement using sqlite3_reset() then go back to step 2 and repeat.
  5. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

删除→记忆 2024-07-18 20:58:27

假设表中有三列: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');

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)。

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')
          );

Assuming three columns in the table: ID, NAME, ROLE


BAD: This will insert or replace all columns with new values for ID=1:

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

BAD: This will insert or replace 2 of the columns... the NAME column will be set to NULL or the default value:

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

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.

enter image description here

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).

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

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 INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );
め七分饶幸 2024-07-18 20:58:27

INSERT OR REPLACE等同于“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 的预期输出是更改角色并保留名称。

INSERT OR REPLACE is NOT equivalent to "UPSERT".

Say I have the table Employee with the fields id, name, and 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, 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.

极度宠爱 2024-07-18 20:58:27

如果您只想保留现有行中的一列或两列,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 列在所有方面都完全缺失 - 因为它有一个 DEFAULT 值,SQLite 在任何情况下都会做正确的事情,所以我没有我自己来照顾它。

您还可以在newold 两侧都包含一列,然后在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:

 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
 );

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 its name, I want it to continue to have the old ID value (obviously!).

I achieve a true UPSERT with the following construct:

 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;

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 be NULL 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 a DEFAULT 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 and old sides and then use e.g. COALESCE(new.content, old.content) in the outer SELECT 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.

过气美图社 2024-07-18 20:58:27

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

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.

enter image description here

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.

寻找一个思念的角度 2024-07-18 20:58:27

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

  1. 开始一个事务
  2. 进行更新
  3. 检查行数
  4. 如果为 0,则执行插入
  5. 提交

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

  1. 开始一个事务
  2. 尝试插入
  3. 检查主键违规错误
  4. (如果出现)更新时发生错误
  5. 提交

这样您就可以避免选择并且在 Sqlite 上事务性良好。

If you are generally doing updates I would ..

  1. Begin a transaction
  2. Do the update
  3. Check the rowcount
  4. If it is 0 do the insert
  5. Commit

If you are generally doing inserts I would

  1. Begin a transaction
  2. Try an insert
  3. Check for primary key violation error
  4. if we got an error do the update
  5. Commit

This way you avoid the select and you are transactionally sound on Sqlite.

风柔一江水 2024-07-18 20:58:27

我意识到这是一个旧线程,但我最近一直在 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;

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:

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>; 

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.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;
待"谢繁草 2024-07-18 20:58:27

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

来自文档

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


示例:

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 演示

Beginning with version 3.24.0 UPSERT is supported by SQLite.

From the documentation:

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. UPSERT syntax was added to SQLite with version 3.24.0 (pending).

An UPSERT is an ordinary INSERT statement that is followed by the special ON CONFLICT clause

enter image description here

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


Example:

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

婴鹅 2024-07-18 20:58:27

Bernhardt 的更新:

您确实可以在 SQLite 中执行更新插入,只是看起来与您习惯的有点不同。 它看起来像:

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

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:

INSERT INTO table_name (id, column1, column2) 
VALUES ("youruuid", "value12", "value2")
ON CONFLICT(id) DO UPDATE 
SET column1 = "value1", column2 = "value2"
才能让你更想念 2024-07-18 20:58:27

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

它的工作原理如下:
1. 如果存在相同Id的记录则尝试更新。
2. 如果更新未更改任何行 (NOT EXISTS(SELECTchanges() ASchangeFROMContactWHEREchange<>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;

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:

--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;

Test insert:

--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;
凉风有信 2024-07-18 20:58:27

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

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

--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
                );

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.

--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
                );
从﹋此江山别 2024-07-18 20:58:27

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

我还使该示例在 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";

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.

 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";
陪我终i 2024-07-18 20:58:27

如果有人想阅读我在 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插件的使用请参考官方链接

If someone wants to read my solution for SQLite in Cordova, I got this generic js method thanks to @david answer above.

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");
        });
    });
});
}

So, first pick up the column names with this function:

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);
    });
});
}

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

撩起发的微风 2024-07-18 20:58:27

遵循亚里士多德Pagaltzis以及来自Eric B 的回答,这里是一个更新插入选项,用于仅更新几列或插入整行(如果不存在)。

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

NOTE id 强制为 NULL当 INSERT 时,因为它应该是自动增量。 如果它只是生成的主键,则也可以使用COALESCE(请参阅亚里士多德·帕加尔茨评论)。

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

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 when INSERT as it is supposed to be autoincrement. If it is just a generated primary key then COALESCE can also be used (see 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;

So the general rule would be, if you want to keep old values, use COALESCE, when you want to update values, use new.fieldname

紫南 2024-07-18 20:58:27

我想这可能就是您正在寻找的:ON CONFLICT 条款

如果你像这样定义你的表:

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

现在,如果你使用已经存在的 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:

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

Now, if you do an INSERT with an id that already exists, SQLite automagically does UPDATE instead of INSERT.

Hth...

夜清冷一曲。 2024-07-18 20:58:27

此方法重新混合了该问题答案中的一些其他方法,并结合了 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 函数中的空值替换为应有的值来决定要使用的默认值。

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:

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
;

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.

茶色山野 2024-07-18 20:58:27

如果您不介意分两次执行此操作。

步骤:

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.

╰つ倒转 2024-07-18 20:58:27

使用 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;

Complete example of upserting using WHERE to select the newer dated record.

-- 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;
情绪 2024-07-18 20:58:27

刚刚读完这篇文章,并对这个“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!

好多鱼好多余 2024-07-18 20:58:27
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;
SELECT COUNT(*) FROM table1 WHERE id = 1;

if 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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文