Postgres: INSERT, если еще не существует - PullRequest
268 голосов
/ 01 ноября 2010

Я использую Python для записи в базу данных postgres:

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

Но поскольку некоторые из моих строк идентичны, я получаю следующую ошибку:

psycopg2.IntegrityError: duplicate key value  
  violates unique constraint "hundred_pkey"

Как я могу написать оператор SQL INSERT, если эта строка уже не существует?

Я видел сложные утверждения, как это рекомендуется:

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

Но, во-первых, является ли это излишним для того, что мне нужно, а во-вторых, как мне выполнить одну из них в виде простой строки?

Ответы [ 17 ]

344 голосов
/ 12 ноября 2012

Как я могу написать оператор SQL INSERT, если эта строка уже не существует?

В PostgreSQL есть хороший способ сделать условную INSERT:

INSERT INTO example_table
    (id, name)
SELECT 1, 'John'
WHERE
    NOT EXISTS (
        SELECT id FROM example_table WHERE id = 1
    );

CAVEAT Однако этот подход не на 100% надежен для одновременных операций записи. Существует очень крошечная раса между SELECT в NOT EXISTS anti-semi-join и самим INSERT. может выйти из строя при таких условиях.

340 голосов
/ 31 июля 2015

Postgres 9.5 (выпущено с 2016-01-07) предлагает команду "upsert" , также известную как предложение ON CONFLICT для INSERT :

INSERT ... ON CONFLICT DO NOTHING/UPDATE

Он решает многие тонкие проблемы, с которыми вы можете столкнуться при использовании параллельной операции, что предлагают некоторые другие ответы.

45 голосов
/ 01 ноября 2010

Один из подходов заключается в создании неограниченной (без уникальных индексов) таблицы для вставки всех ваших данных и выбора, отличного от этого, для вставки в вашу таблицу сот.

Такой высокий уровень был бы. Я предполагаю, что все три столбца различны в моем примере, поэтому для шага 3 измените объединение NOT EXITS, чтобы объединить только уникальные столбцы в таблице сотен.

  1. Создать временную таблицу. См. Документы здесь .

    CREATE TEMPORARY TABLE temp_data(name, name_slug, status);
    
  2. Вставить данные во временную таблицу.

    INSERT INTO temp_data(name, name_slug, status); 
    
  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
        );
    
16 голосов
/ 01 ноября 2010

К сожалению, 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
        )

Вы можете заключить его в функцию:

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

и просто назовите это:

SELECT  fn_upd_invoices('12345', 'TRUE')
10 голосов
/ 30 марта 2012

Вы можете использовать ЦЕННОСТИ - доступно в Postgres:

INSERT INTO person (name)
    SELECT name FROM person
    UNION 
    VALUES ('Bob')
    EXCEPT
    SELECT name FROM person;
7 голосов
/ 21 мая 2012

Я знаю, что этот вопрос задолго до того, но подумал, что это может кому-то помочь. Я думаю, что самый простой способ сделать это через триггер. E.g.:

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

Выполните этот код из приглашения psql (или если вы хотите выполнять запросы непосредственно в базе данных). Затем вы можете вставить как обычно из Python. E.g.:

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

Обратите внимание, что, как уже упоминалось в @Thomas_Wouters, приведенный выше код использует преимущества параметров, а не объединяет строку.

6 голосов
/ 14 октября 2017

Есть хороший способ сделать условную INSERT в PostgreSQL с помощью запроса WITH: Как:

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 
4 голосов
/ 26 марта 2015

ВСТАВИТЬ. ГДЕ НЕ СУЩЕСТВУЕТ - это хороший подход. А условия гонки можно избежать с помощью «конверта» транзакции:

BEGIN;
LOCK TABLE hundred IN SHARE ROW EXCLUSIVE MODE;
INSERT ... ;
COMMIT;
2 голосов
/ 12 июля 2016

С правилами легко:

CREATE RULE file_insert_defer AS ON INSERT TO file
WHERE (EXISTS ( SELECT * FROM file WHERE file.id = new.id)) DO INSTEAD NOTHING

Но сбой при одновременной записи ...

1 голос
/ 21 февраля 2019

Если вы скажете, что многие ваши строки идентичны, вы прекратите проверку много раз. Вы можете отправить их, и база данных определит, вставить это или нет, с помощью предложения ON CONFLICT следующим образом

  INSERT INTO Hundred (name,name_slug,status) VALUES ("sql_string += hundred  
  +",'" + hundred_slug + "', " + status + ") ON CONFLICT ON CONSTRAINT
  hundred_pkey DO NOTHING;" cursor.execute(sql_string);
...