Postgres:INSERT如果不存在

AP2*_*257 304 python postgresql sql-insert

我正在使用Python写入postgres数据库:

sql_string = "INSERT INTO hundred (name,name_slug,status) VALUES ("
sql_string += hundred + ", '" + hundred_slug + "', " + status + ");"
cursor.execute(sql_string)
Run Code Online (Sandbox Code Playgroud)

但由于我的一些行是相同的,我收到以下错误:

psycopg2.IntegrityError: duplicate key value  
  violates unique constraint "hundred_pkey"
Run Code Online (Sandbox Code Playgroud)

如何编写'INSERT除非此行已存在'SQL语句?

我见过这样的复杂陈述:

IF EXISTS (SELECT * FROM invoices WHERE invoiceid = '12345')
UPDATE invoices SET billed = 'TRUE' WHERE invoiceid = '12345'
ELSE
INSERT INTO invoices (invoiceid, billed) VALUES ('12345', 'TRUE')
END IF
Run Code Online (Sandbox Code Playgroud)

但首先,这对于我需要的东西是否过度,其次,我怎样才能将其中一个作为简单的字符串执行?

Ari*_*rie 401

Postgres 9.5(自2016-01-07发布)提供了一个"upsert"命令,也称为INSERTON CONFLICT子句:

INSERT ... ON CONFLICT DO NOTHING/UPDATE
Run Code Online (Sandbox Code Playgroud)

它解决了使用并发操作时可能遇到的许多微妙问题,其他一些答案提出了这些问题.

  • 9.5发布了. (14认同)
  • 对于那些需要的人,这里有两个简单的例子.(1)INSERT如果不存在,否则NOTHING - `INSERT INTO经销商(确实,dname)VALUES(7,'Redline GmbH')冲突(确实)没有;`(2)INSERT如果不存在其他UPDATE - `INSERT INTO经销商(确实,dname)VALUES(5,'Gizmo Transglobal'),(6,'Associated Computing,Inc')ON CONFLICT(确实)DO UPDATE SET dname = EXCLUDED.dname;`这些例子来自手册 - https:/ /www.postgresql.org/docs/9.5/static/sql-insert.html (9认同)
  • 有一个警告/副作用.在具有序列列(serial或bigserial)的表中,即使没有插入行,序列也会在每次插入尝试时递增. (9认同)
  • @TusharJain在PostgreSQL 9.5之前你可以做一个"老式"UPSERT(带CTE),但你可能会遇到竞争条件问题而且它不会像9.5风格一样高效.关于upsert的详细信息[博客](https://hashrocket.com/blog/posts/upsert-records-with-postgresql-9-5)(在底部的更新区域)包括一些链接你想要阅读更多关于细节的信息. (2认同)
  • 最好是链接到INSERT文档而不是指向发布.Doc链接:https://www.postgresql.org/docs/9.5/static/sql-insert.html (2认同)
  • 如果您需要“在冲突中不要返回ID”,请阅读此答案/sf/answers/2955251071/。 (2认同)
  • @GrzegorzLuczywo 无论如何,序列号是否会增加? (2认同)

Joh*_*Doe 357

如何编写'INSERT除非此行已存在'SQL语句?

有一种在PostgreSQL中进行条件INSERT的好方法:

INSERT INTO example_table
    (id, name)
SELECT 1, 'John'
WHERE
    NOT EXISTS (
        SELECT id FROM example_table WHERE id = 1
    );
Run Code Online (Sandbox Code Playgroud)

CAVEAT但是,对于并发写操作,这种方法并非100%可靠.SELECTNOT EXISTS反半连接和INSERT自身之间存在非常微小的竞争条件.在这种情况下它可能会失败.

  • 我发现这是不可靠的。似乎Postgres有时会在执行选择之前执行插入操作,即使记录尚未插入,我也会遇到重复的键冲突。尝试对ON CONFLICT使用version => 9.5。 (3认同)
  • 这很好用.唯一的问题是我想的耦合:如果一个修改表,使更多的列是唯一的.在这种情况下,必须修改所有脚本.如果有更通用的方法来做这件事会很好...... (2认同)
  • @OlivierPons是的,这是可能的.在查询和查询处添加`RETURNING id`,如果没有插入行,它将返回新的行ID或什么也不返回. (2认同)

Kub*_*aun 47

一种方法是创建一个非约束(无唯一索引)表,将所有数据插入并执行与此不同的选择,以插入到您的百表中.

如此高水平.我假设在我的示例中所有三列都是不同的,因此对于step3,将NOT EXITS连接更改为仅连接到百表中的唯一列.

  1. 创建临时表.请参阅此处的文档.

    CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将数据插入临时表.

    INSERT INTO temp_data(name, name_slug, status); 
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将任何索引添加到临时表.

  4. 主表插入.

    INSERT INTO hundred(name, name_slug, status) 
        SELECT DISTINCT name, name_slug, status
        FROM hundred
        WHERE NOT EXISTS (
            SELECT 'X' 
            FROM temp_data
            WHERE 
                temp_data.name          = hundred.name
                AND temp_data.name_slug = hundred.name_slug
                AND temp_data.status    = status
        );
    
    Run Code Online (Sandbox Code Playgroud)

  • 当我不知道该行是否已存在时,这是我发现进行大量插入的最快方法. (3认同)
  • 查找相关子查询.'X'可以改为1甚至'SadClown'.SQL要求有一些东西,'X'是常用的东西.它很小,很明显正在使用相关的子查询,并满足SQL所需的要求. (3认同)
  • 您提到“将所有数据插入(假设为临时表)并执行与该表不同的选择”。在那种情况下,它不应该是`SELECT DISTINCT name, name_slug, status FROM temp_data`吗? (2认同)

Qua*_*noi 16

不幸的是,PostgreSQL既不支持MERGE也不支持ON DUPLICATE KEY UPDATE,所以你必须在两个语句中做到这一点:

UPDATE  invoices
SET     billed = 'TRUE'
WHERE   invoices = '12345'

INSERT
INTO    invoices (invoiceid, billed)
SELECT  '12345', 'TRUE'
WHERE   '12345' NOT IN
        (
        SELECT  invoiceid
        FROM    invoices
        )
Run Code Online (Sandbox Code Playgroud)

你可以把它包装成一个函数:

CREATE OR REPLACE FUNCTION fn_upd_invoices(id VARCHAR(32), billed VARCHAR(32))
RETURNS VOID
AS
$$
        UPDATE  invoices
        SET     billed = $2
        WHERE   invoices = $1;

        INSERT
        INTO    invoices (invoiceid, billed)
        SELECT  $1, $2
        WHERE   $1 NOT IN
                (
                SELECT  invoiceid
                FROM    invoices
                );
$$
LANGUAGE 'sql';
Run Code Online (Sandbox Code Playgroud)

然后打电话给它:

SELECT  fn_upd_invoices('12345', 'TRUE')
Run Code Online (Sandbox Code Playgroud)


tua*_*ptn 13

这正是我面临的问题,我的版本是 9.5

我用下面的 SQL 查询解决了它。

INSERT INTO example_table (id, name)
SELECT 1 AS id, 'John' AS name FROM example_table
WHERE NOT EXISTS(
            SELECT id FROM example_table WHERE id = 1
    )
LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

希望对版本>= 9.5 有相同问题的人有所帮助。

谢谢阅读。

  • 这个答案与@John Doe 相同,其中指出了并发写入操作的警告。 (5认同)

cri*_*stm 11

你可以使用VALUES - Postgres中提供的:

INSERT INTO person (name)
    SELECT name FROM person
    UNION 
    VALUES ('Bob')
    EXCEPT
    SELECT name FROM person;
Run Code Online (Sandbox Code Playgroud)

  • 选择名称FROM Person <---如果有人行十亿行怎么办? (11认同)
  • 我认为这是解决问题的一个很好的快速方法,但前提是您确定源表永远不会变大。我有一个永远不会超过 1000 行的表,所以我可以使用这个解决方案。 (2认同)

小智 8

我们可以使用 upsert 来简化查询

insert into invoices (invoiceid, billed) 
  values ('12345', 'TRUE') 
  on conflict (invoiceid) do 
    update set billed=EXCLUDED.billed;
Run Code Online (Sandbox Code Playgroud)


ktr*_*ktr 7

我知道这个问题是从不久前开始的,但我认为这可能对某人有所帮助.我认为最简单的方法是通过触发器.例如:

Create Function ignore_dups() Returns Trigger
As $$
Begin
    If Exists (
        Select
            *
        From
            hundred h
        Where
            -- Assuming all three fields are primary key
            h.name = NEW.name
            And h.hundred_slug = NEW.hundred_slug
            And h.status = NEW.status
    ) Then
        Return NULL;
    End If;
    Return NEW;
End;
$$ Language plpgsql;

Create Trigger ignore_dups
    Before Insert On hundred
    For Each Row
    Execute Procedure ignore_dups();
Run Code Online (Sandbox Code Playgroud)

从psql提示符执行此代码(或者您希望直接在数据库上执行查询).然后你可以从Python正常插入.例如:

sql = "Insert Into hundreds (name, name_slug, status) Values (%s, %s, %s)"
cursor.execute(sql, (hundred, hundred_slug, status))
Run Code Online (Sandbox Code Playgroud)

请注意,正如@Thomas_Wouters已经提到的,上面的代码利用了参数而不是连接字符串.


Rit*_*Jha 6

有一种使用WITH查询在PostgreSQL中进行条件INSERT的好方法:喜欢:

WITH a as(
select 
 id 
from 
 schema.table_name 
where 
 column_name = your_identical_column_value
)
INSERT into 
 schema.table_name
(col_name1, col_name2)
SELECT
    (col_name1, col_name2)
WHERE NOT EXISTS (
     SELECT
         id
     FROM
         a
        )
  RETURNING id 
Run Code Online (Sandbox Code Playgroud)


Pav*_*rek 5

插入..不存在是一个好方法。交易条件可以通过交易“信封”来避免:

BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
Run Code Online (Sandbox Code Playgroud)