как эмулировать" insert ignore "и" on duplicate key update " (слияние sql) с postgresql?

некоторые SQL-серверы имеют функцию, где INSERT пропускается, если это нарушит ограничение первичного / уникального ключа. Например, MySQL имеет INSERT IGNORE.

какой лучший способ подражать INSERT IGNORE и ON DUPLICATE KEY UPDATE С PostgreSQL?

11 ответов


попробуйте сделать обновление. Если он не изменяет какую-либо строку, это означает, что она не существовала, поэтому сделайте вставку. Очевидно, вы делаете это внутри транзакции.

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

в документации есть пример этого:http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html, пример 40-2 справа внизу.

это обычно самый простой способ. Вы можете делать магию с правилами, но это, вероятно, будет намного грязнее. Я бы рекомендовал подход wrap-in-function в любой день.

Это работает для одной строки или нескольких строк, значения. Если вы имеете дело с большим количеством строк, например, из подзапроса, вам лучше разделить его на два запроса: один для вставки и один для обновления (как соответствующее соединение / подзапрос, конечно , - нет необходимости напишите свой основной фильтр дважды)


С PostgreSQL 9.5, это теперь стандартные функции (как MySQL имел в течение нескольких лет):

вставить ... ПРИ КОНФЛИКТЕ НИЧЕГО НЕ ДЕЛАТЬ / ОБНОВИТЬ ("UPSERT")

9.5 обеспечивает поддержку операций "UPSERT". Вставка расширена, чтобы принять предложение on CONFLICT DO UPDATE/IGNORE. Данный пункт определяет альтернативные действия в случае будут дублировать нарушение.

...

дальнейший пример нового синтаксиса:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

Edit: в случае, если вы пропустили Уоррен, PG9.5 теперь имеет Это изначально; время для обновления!


основываясь на ответе Билла Карвина, чтобы объяснить, как будет выглядеть подход на основе правил (передача из другой схемы в той же БД и с многоколоночным первичным ключом):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

Примечание: правило применяется ко всем INSERT операции до тех пор, пока правило не будет удалено, поэтому не совсем ad hoc.


для получения вставить игнорировать логики вы можете сделать что-то вроде ниже. Я нашел, что просто вставка из инструкции select литеральных значений работала лучше всего, тогда вы можете замаскировать дубликаты ключей предложением NOT EXISTS. Чтобы получить обновление дублирующей логики, я подозреваю, что потребуется цикл pl/pgsql.

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

для тех из вас, кто имеет Postgres 9.5 или выше, новый НА КОНФЛИКТ НИЧЕГО НЕ ДЕЛАТЬ синтаксис должен работать:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

для тех из нас, кто имеет более раннюю версию, это право присоединиться будут работать вместо:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

похоже, PostgreSQL поддерживает объект схемы, называемый правила.

http://www.postgresql.org/docs/current/static/rules-update.html

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

Я не пробовал это сам, поэтому я не могу говорить из опыта или предложите пример.


это решение позволяет избежать использования правил:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

но у него есть недостаток производительности (см. PostgreSQL.org):

блок, содержащий предложение исключения, значительно дороже чтобы войти и выйти, чем блок без одного. Поэтому не используйте Исключение без необходимости.


как @hanmari упомянул в своем комментарии. при вставке в таблицы postgres, on conflict (..) do nothing-лучший код для использования, чтобы не вставлять дубликаты данных.:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

строка кода on CONFLICT позволит инструкции insert по-прежнему вставлять строки данных. Код запроса и значений является примером вставленной даты из Excel в таблицу БД postgres. У меня есть ограничения, добавленные в таблицу postgres, которую я использую, чтобы убедиться, что поле ID уникально. Вместо запуска удаление в строках данных, которые одинаковы, я добавляю строку кода sql, которая переименовывает столбец ID, начиная с 1. Пример:

q = 'ALTER id_column serial RESTART WITH 1'

Если мои данные имеют поле ID, я не использую это в качестве основного ID / serial ID, я создаю столбец ID и устанавливаю его в serial. Надеюсь, эта информация будет полезна всем. *У меня нет диплома колледжа по разработке/кодированию программного обеспечения. Все, что я знаю в кодировании, я изучаю сам.


в bulk вы всегда можете удалить строку перед вставкой. Удаление строки, которая не существует, не вызывает ошибки, поэтому ее безопасно пропустили.


для скриптов импорта данных, чтобы заменить "если не существует", в некотором роде, есть немного неудобная формулировка, которая тем не менее работает:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;