как имитировать «вставку игнорирования» и «при обновлении дублирующего ключа» (слияние sql) с помощью postgresql?

148

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

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

гпилотино
источник
См. Также: stackoverflow.com/questions/5269590/…
Дэйв Джарвис,
6
с 9.5 это возможно изначально: stackoverflow.com/a/34639631/4418
Уоррен
Эмуляция MySQL: ON DUPLICATE KEY UPDATEв PgSQL 9.5 все еще в некоторой степени невозможно, потому что ON CLAUSEэквивалент PgSQL требует, чтобы вы указали имя ограничения, в то время как MySQL может захватывать любое ограничение без необходимости его определения. Это не позволяет мне «эмулировать» эту функцию без переписывания запросов.
NeverEndingQueue

Ответы:

37

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

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

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

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

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

Магнус Хагандер
источник
4
«Если вы имеете дело с большим количеством строк», это как раз мой случай. Я хочу массово обновлять / вставлять строки, и с mysql я могу сделать это только с ОДНИМ запросом без какого-либо цикла. Теперь мне интересно, возможно ли это и с postgresql: использовать только один запрос для массового обновления ИЛИ вставки. Вы говорите: «Лучше всего разбить его на два запроса, один для INSERT и один для UPDATE», но как я могу сделать вставку, которая не вызывает ошибок на повторяющихся ключах? (т.е. «
ВСТАВИТЬ ИГНОРИРОВАНИЕ
4
Магнус имел в виду, что вы используете такой запрос: «начать транзакцию; создать временную таблицу временную_таблицу как выбрать * из теста, где ложно; скопировать временную_таблицу из файла 'data_file.csv'; заблокировать тест таблицы; обновить тестовый набор данных = временная_таблица. Данные из временной_таблицы, где test.id = временная_таблица.id; вставить в тест «выберите * из временной_таблицы, где идентификатор отсутствует (выберите идентификатор из теста) как»
Tometzky
28
Обновление: с PostgreSQL 9.5 это теперь так же просто, как INSERT ... ON CONFLICT DO NOTHING;. См. Также ответ stackoverflow.com/a/34639631/2091700 .
Alphaaa
Важно, SQL-стандарт MERGEявляется не параллелизм безопасным upsert, если не принять LOCK TABLEпервый. Люди так используют, но это неправильно.
Крэйг Рингер
1
В версии 9.5 это теперь «родная» функция, поэтому, пожалуйста, проверьте комментарий @Alphaaa (просто рекламируя комментарий, рекламирующий ответ)
Камило Делвасто,
186

В 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;
Уоррен
источник
100

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

EoghanM
источник
@sema вы имеете в виду, если another_schema.my_tableсодержит дубликаты в соответствии с ограничениями my_table?
EoghanM
2
@EoghanM Я тестировал правило в postgresql 9.3 и все еще мог вставлять дубликаты с помощью операторов вставки нескольких строк, например, INSERT INTO "my_table" (a, b), (a, b); (Предполагая, что строка (a, b) еще не существует в "my_table".)
sema
@sema, gotcha - это должно означать, что правило выполняется в начале для всех данных, которые нужно вставить, и не выполняется повторно после вставки каждой строки. Один из подходов - сначала вставить ваши данные в другую временную таблицу, у которой нет никаких ограничений, а затем сделать этоINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM
@EoghanM Другой подход - временно ослабить ограничения на дублирование и принять дубликаты при вставке, но потом удалить дубликаты с помощьюDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema
У меня проблема, описанная @sema. Если я сделаю вставку (a, b), (a, b), это вызовет ошибку. Есть ли способ подавить ошибки и в этом случае?
Diogo Melo
40

Для тех из вас, у кого 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;
Ханмари
источник
Второй подход не работает при создании большой вставки в параллельной среде. Вы получите, Unique violation: 7 ERROR: duplicate key value violates unique constraintкогда в target_tableнего была вставлена ​​другая строка во время выполнения этого запроса, если их ключи действительно дублируют друг друга. Я считаю, что блокировка target_tableпоможет, но явно пострадает параллелизм.
Г. Каштанов
2
ON CONFLICT (field_one) DO NOTHINGэто лучшая часть ответа.
Abel
Реквизит для ON CONFLIT DO NOTHING. Очень понравилось
Д. Мело
24

Чтобы получить логику игнорирования вставки, вы можете сделать что-то вроде ниже. Я обнаружил, что лучше всего работает простая вставка буквальных значений из оператора 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)
)
Keyo
источник
Что, если tmp содержит повторяющуюся строку, что может произойти?
Хенли Чиу
Вы всегда можете выбрать с помощью отдельного ключевого слова.
Keyo 04
5
Как и FYI, уловка «WHERE NOT EXISTS» не работает для нескольких транзакций, потому что разные транзакции не могут видеть новые добавленные данные из других транзакций.
Дэйв Йохансен
21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')
user2342158
источник
Каково влияние нескольких транзакций, пытающихся сделать одно и то же? Возможно ли, что между выполнением, где не существует, и вставкой, выполняющей какую-либо другую транзакцию, вставляет строку? И если Postgres может предотвратить это, то разве postgres не вводит точку синхронизации для всех этих транзакций, когда они достигают этого?
Καrτhικ
Это не работает с несколькими транзакциями, потому что новые добавленные данные не видны другим транзакциям.
Дэйв Йохансен,
13

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

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

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

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

Билл Карвин
источник
1
если я хорошо понял, эти правила являются триггерами, которые выполняются каждый раз, когда вызывается оператор. что, если я хочу применить правило только для одного запроса? я должен создать правило, а затем немедленно его уничтожить? (как насчет условий гонки?)
gpilotino
3
Да, у меня тоже будут те же вопросы. Механизм правил - это то, что я смог найти в PostgreSQL, ближе всего к MySQL INSERT IGNORE или ON DUPLICATE KEY UPDATE. Если мы введем в Google запрос «postgresql при обновлении дублированного ключа», вы обнаружите, что другие люди рекомендуют механизм правил, даже если правило будет применяться к любому INSERT, а не только на разовой основе.
Билл Карвин,
4
PostgreSQL поддерживает транзакционный DDL, что означает, что если вы создадите правило и отбросите его в рамках одной транзакции, правило никогда не будет видимым вне этой транзакции (и, следовательно, никогда не будет иметь никакого эффекта за пределами этой транзакции).
cdhowie
7

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

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'

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

Yankeeownz
источник
это не работает с составными уникальными индексами!
Нулик,
4

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

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

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

Дэвид Норьега
источник
2
Такой подход будет весьма подвержен странным условиям гонки, я бы не рекомендовал его ...
Стивен Шланскер
1
+1 Это просто и типично. При осторожном использовании это может быть простым решением.
Воутер ван Нифтерик,
1
Это также не будет работать, если существующие данные были изменены после вставки (но не на дублирующем ключе), и мы хотим сохранить обновления. Это сценарий, когда есть сценарии SQL, которые написаны для ряда немного разных систем, например, обновления базы данных, которые выполняются в производственных системах, системах контроля качества, разработке и тестировании.
Hanno Fietz
1
Внешний ключ может быть без проблем, если вы создадите их с DEFERRABLE INITIALLY DEFERREDфлагами.
temoto
-1

Для скриптов импорта данных, чтобы заменить «ЕСЛИ НЕ СУЩЕСТВУЕТ», в некотором смысле есть несколько неудобная формулировка, которая, тем не менее, работает:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
analytik_work
источник