几个月前,我从 Stack Overflow 上的一个答案中了解到如何使用以下语法在 MySQL 中同时执行多个更新:

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

我现在已经切换到 PostgreSQL,显然这是不正确的。它指的是所有正确的表,所以我认为这是使用不同关键字的问题,但我不确定 PostgreSQL 文档中的哪个部分涵盖了这一点。

为了澄清,我想插入一些东西,如果它们已经存在则更新它们。

有帮助吗?

解决方案

PostgreSQL 从 9.5 版本开始有 更新插入 语法,与 关于冲突 条款。 语法如下(类似于MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

在 postgresql 的电子邮件组档案中搜索“upsert”会发现 手册中执行您可能想做的事情的示例:

实施例38-2。更新/插入的异常

此示例根据需要使用异常处理来执行 UPDATE 或 INSERT:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

可能有一个关于如何使用 9.1 及更高版本中的 CTE 批量执行此操作的示例,位于 黑客邮件列表:

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

a_horse_with_no_name 的回答 举一个更清楚的例子。

其他提示

警告:如果在同一时间从多个会话执行这不是安全(见下文警告)


另一种巧妙的方式PostgreSQL中做一个“UPSERT”是做两个顺序UPDATE / INSERT语句,它们各自设计成接替或没有任何效果。

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

的更新将成功,如果与行“ID = 3”已存在,否则没有任何效果。

在INSERT将成功仅当行与 “ID = 3” 不存在。

您可以将这两个合并成一个字符串,并用一个SQL语句运行它们无论是从你的应用程序中执行。在单个事务中一起运行它们时,强烈推荐

这个工程时,隔离或锁定表上运行得非常好,但受种族意味着它可能仍然会失败,重复的键错误,如果某一行同时插入,或者当行可能没有插入行终止条件同时被删除。 PostgreSQL的9.1或更高的SERIALIZABLE交易将可靠地处理它以非常高的序列化失败率的成本,这意味着你将不得不重试了很多。请参见为什么UPSERT这么复杂,该更详细地讨论了这种情况。

除非应用程序检查受影响的行计数该方法也read committed隔离href="https://dba.stackexchange.com/q/78510/7788">受丢失更新和验证要么insertupdate影响了行。

与PostgreSQL9.1本可以实现采用一个可写入CTE(常见的表达):

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

看看这些博客项:


注意,这一解决方案不会 防止一个独特的关键的违反,但它不是容易受到失去的更新。
看看 后续由克雷格*铃声dba.stackexchange.com

在PostgreSQL9.5和更新可以使用 INSERT ... ON CONFLICT UPDATE.

看看 该文件.

MySQL INSERT ... ON DUPLICATE KEY UPDATE 可以直接改写到 ON CONFLICT UPDATE.无论是SQL标准的语法,它们都是特定数据库的扩展。 有很好的理由 MERGE 不用为这个, 新法并不是创造只是为了乐趣。(MySQL的语法也有问题,意味着它不是通过直接).

例如鉴于设置:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

MySQL查询:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

变成:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

差异:

  • 你的 必须 指定的列名(或独特的约束名)使用独特性检查。这就是的 ON CONFLICT (columnname) DO

  • 关键字 SET 必须使用,因为如果这是一个正常的 UPDATE 声明

它有一些不错的功能:

  • 你可以有一个 WHERE 条款上你 UPDATE (让你有效地转 ON CONFLICT UPDATEON CONFLICT IGNORE 对于某些值)

  • 拟议的对插入值可作为行变 EXCLUDED, ,这具有同样的结构作为目标表。你可以得到的原始数值在表格的使用表格的名称。因此,在这种情况下 EXCLUDED.c10 (因为这是我们试图插入)和 "table".c3 因为这就是目前的价值在表格中。你可以使用一个或两个在 SET 表情和 WHERE 条款。

为背景,在更新插入看到 如何更新插入(合并,并插入...关于重复的更新)在PostgreSQL?

我一直在寻找同样的事情,当我来到这里,但由于缺乏一个通用的“更新插入”功能botherd我一下,让我以为你可以只通过更新和插入SQL作为该函数的参数形成手动

这应该是这样的:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

也许做您最初想做的事,一批“更新插入”,你可以使用Tcl的分裂SQL_UPDATE和循环的各个更新,在性能与打击将是非常小的看的 http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php

的最高成本正在执行从代码查询,在数据库端的执行成本要小得多

没有简单的命令可以做到这一点。

最正确的方法是使用函数,就像来自 文档.

另一个解决方案(虽然不是那么安全)是在返回时进行更新,检查哪些行已更新,然后插入其余行

大致如下:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

假设返回 id:2:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

当然,它迟早会退出(在并发环境中),因为这里存在明显的竞争条件,但通常它会起作用。

这是一个 关于该主题的更长、更全面的文章.

就个人而言,我已经建立了一个“规则”连接到插入语句。假设你有这样的记录每个客户的DNS命中在每个时间基础上“DNS”表:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

您希望能够重新插入行使用更新的值,或创建他们,如果他们不存在。键控的CUSTOMER_ID和时间。是这样的:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

更新:这具有如果同时插入件发生,失败,因为它会产生异常unique_violation的电位。然而,未终止的事务将继续并取得成功,你只需要重复终止交易。

不过,如果有吨插入发生的一切的时候,你会希望把表锁周围的INSERT语句:SHARE ROW EXCLUSIVE锁将防止可能插入,在你的目标表删除或更新行的任何操作。但是,不更新的唯一密钥更新是安全的,所以如果你没有操作会做到这一点,使用咨询锁代替。

此外,COPY命令不使用的规则,所以如果你用COPY插入,你需要使用触发器来代替。

我定义的"更新插入"功能上所述,如果要插入和更换:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

后来执行,这样做:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

重要的是要把双美元的逗号,以避免编译错误。

  • 检查的速度...

到最喜欢的答案相似,但工作速度稍快:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(来源: HTTP://www.the-art-of- web.com/sql/upsert/

我来管理帐户设置为名称值对同样的问题。 设计标准是,不同的客户端可以有不同的设置集。

我的解决办法中,类似于JWP是批量擦除和替换,应用程序内生成合并记录。

这是相当防弹,平台独立,因为有从来没有超过每客户约20设置的更多,这仅仅是3相当低负荷分贝电话 - 可能是最快的方法。

更新各行的选择 - 检查异常然后插入 - 或的一些组合是可怕的代码,速度慢,经常断裂,因为(如上文所提到的)非标准SQL异常处理从分贝改变为分贝 - 或者甚至释放以释放

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION

根据该 PostgreSQL文件 INSERT 声明, 处理 ON DUPLICATE KEY 情况不支持。这一部分的语法是一个专有的MySQL扩展。

CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT

我使用此功能合并

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql

合并的小组,利用上述的功能是罚款。但是,如果是合并,大量数据,我建议寻找到 http://mbk.projects.postgresql.org

当前最佳做法,我知道的是:

  1. 复制新的/更新数据纳入临时表(确定,或者你可以做插入如果费用是"确定")
  2. 获得锁[可选择](咨询是最好的表锁,国际海事组织)
  3. 合并。(最有趣的部分)

UPDATE将返回修改的行的数量。如果你使用JDBC(Java),然后你可以反对0检查这个值,如果没有行受到了影响,火INSERT代替。如果你使用一些其他的编程语言,也许还可以得到修改后的行数,检查文档。

这可能不是优雅,但你有更简单的SQL是比较琐碎从调用代码中使用。不同的是,如果你写在PL / PSQL十行脚本,你应该有一个单元测试或另一种只是单干。

修改如预期这不起作用。不同于接受的答案,这将产生唯一的密钥侵犯当两个过程反复upsert_foo并发呼叫。

尤里卡!我想出了一个办法做到这一点在一个查询:使用UPDATE ... RETURNING来测试,如果任何行受到影响:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

UPDATE具有在单独的过程来完成,因为,不幸的是,这是一个语法错误:

... WHERE NOT EXISTS (UPDATE ...)

现在它的工作原理根据需要:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top