Как эмулировать "вставить игнорировать" и "при обновлении дубликата ключа" (объединение SQL) с postgresql? - PullRequest
122 голосов
/ 18 июня 2009

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

Какой лучший способ эмулировать INSERT IGNORE и ON DUPLICATE KEY UPDATE с PostgreSQL?

Ответы [ 11 ]

134 голосов
/ 06 января 2016

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

ВСТАВИТЬ ... В КОНФЛИКТ НИЧЕГО / ОБНОВИТЬ ("UPSERT")

9.5 обеспечивает поддержку операций "UPSERT". INSERT расширен для принятия предложения 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;
95 голосов
/ 30 мая 2011

Редактировать: если вы пропустили ответ Уоррена , 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 операциям до тех пор, пока правило не будет удалено, поэтому не является специальным.

30 голосов
/ 18 июня 2009

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

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

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

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

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

23 голосов
/ 29 октября 2016

Для тех из вас, кто имеет Postgres 9.5 или выше, новый ON CONFLICT DO NOTHING синтаксис должен работать:

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;
23 голосов
/ 22 февраля 2012

Чтобы получить insert игнорировать логику , вы можете сделать что-то вроде ниже. Я обнаружил, что вставка из оператора 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)
)
20 голосов
/ 08 мая 2013
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
12 голосов
/ 18 июня 2009

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

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

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

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

3 голосов
/ 07 января 2018

Как отметил @hanmari в своем комментарии. при вставке в таблицы postgres, конфликт on (..) ничего не делает, это лучший код, который нужно использовать для того, чтобы не вставлять дублирующиеся данные .:

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

Строка кода ON CONFLICT позволит оператору вставки вставлять строки данных. Код запроса и значений является примером вставленной даты из Excel в таблицу postgres db. У меня есть ограничения, добавленные в таблицу postgres, которую я использую, чтобы убедиться, что поле идентификатора уникально. Вместо того, чтобы выполнять удаление одинаковых строк данных, я добавляю строку кода SQL, которая перенумеровывает столбец идентификатора, начиная с 1. Пример:

q = 'ALTER id_column serial RESTART WITH 1'

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

2 голосов
/ 16 июня 2014

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

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

Блок, содержащий предложение EXCEPTION, значительно дороже войти и выйти, чем блок без такового. Поэтому не используйте ИСКЛЮЧЕНИЕ без необходимости.

1 голос
/ 12 января 2011

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

...