як емулювати "вставити ігнорування" та "на оновлення дубліката ключа" (злиття sql) за допомогою postgresql?


140

Деякі сервери SQL мають функцію INSERTпропуску, якщо це порушить обмеження основного / унікального ключа. Наприклад, у MySQL є INSERT IGNORE.

Який найкращий спосіб емуляції INSERT IGNOREта ON DUPLICATE KEY UPDATEPostgreSQL?




6
станом на 9.5, це можливо вдома: stackoverflow.com/a/34639631/4418
warren

Емуляція MySQL: ON DUPLICATE KEY UPDATEна PgSQL 9.5 все ще дещо неможливо, оскільки ON CLAUSEеквівалент PgSQL вимагає вказати ім'я обмеження, в той час як MySQL може охопити будь-яке обмеження без необхідності його визначення. Це заважає мені "емулювати" цю функцію без перезаписів запитів.
NeverEndingQueue

Відповіді:


35

Спробуйте зробити ОНОВЛЕННЯ. Якщо він не змінює жодного рядка, це означає, що його не існувало, то зробіть вставку. Очевидно, ви робите це всередині транзакції.

Звичайно, можна зафіксувати це у функції, якщо ви не хочете ставити додатковий код на стороні клієнта. Вам також потрібна петля для дуже рідкісного стану гонки в такому мисленні.

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

Зазвичай це найпростіший спосіб. Ви можете зробити магію з правилами, але це, швидше за все, буде набагато грізніше. Я б рекомендував підходити до завершення роботи будь-якого дня.

Це працює для значень одного ряду або кількох рядків. Якщо ви маєте справу з великою кількістю рядків, наприклад, з підзапиту, вам найкраще розділити його на два запити, один для INSERT і один для UPDATE (як відповідне приєднання / підбір курсу, звичайно - не потрібно писати свій основний фільтрувати двічі)


4
"Якщо ви маєте справу з великою кількістю рядків", це саме мій випадок. Я хочу об'ємно оновлювати / вставляти рядки, і з mysql я можу це зробити лише з одним запитом без жодного циклу. Тепер мені цікаво, чи це можливо і з postgresql: використовувати лише один запит для масового оновлення АБО вставки. Ви кажете: "Вам найкраще розділити його на два запити, один для INSERT і один для UPDATE", але як я можу зробити вставку, яка не кидає помилок на повторювані ключі? (тобто "
ВСТУПИТИ ІГНОР

4
Magnus означав, що ви використовуєте такий запит: "запустити транзакцію; створити тимчасову таблицю тимчасовий таблицю як select * з тесту, де false; скопіювати тимчасовий таблицю з 'data_file.csv'; тест блокування таблиці; оновити тестовий набір даних = тимчасовий_таблик. test.id =vreme_table.id; вставити в тестовий вибір * з тимчасового_таблиці, де id не в (виберіть ідентифікатор з тесту) як "
Tometzky

25
Оновлення: з PostgreSQL 9.5 це тепер так само просто INSERT ... ON CONFLICT DO NOTHING;. Дивіться також відповідь stackoverflow.com/a/34639631/2091700 .
Альфаа

Важливо, SQL-стандарт MERGEє НЕ паралелізм безпечним upsert, якщо не вжити LOCK TABLEперше. Люди використовують це саме так, але це неправильно.
Крейг Рінгер

1
З v9.5 тепер це "рідна" функція, тому, будь ласка, перевірте коментар @Alphaaa (просто рекламуючи коментар, який рекламує відповідь)
Camilo Delvasto

178

З 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операцій до тих пір, поки правило не буде скасовано, тому не зовсім ad hoc.


@sema ти маєш на увазі, якщо another_schema.my_tableмістить дублікати відповідно до обмежень my_table?
EoghanM

2
@EoghanM Я перевірив правило в postgresql 9.3 і все ще можу вставити дублікати з декількома операторами вставки рядків, наприклад, ВСТАВИТИСЯ В "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), вона видає помилку. Чи є спосіб придушити помилки, також у цьому випадку?
Діого Мело

35

Для тих із вас, хто має Postgres 9.5 або вище, новий синтаксис ON CONFLICT NOT 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допоможе, але паралельність явно постраждає.
Г. Каштанов

1
ON CONFLICT (field_one) DO NOTHING- найкраща частина відповіді.
Abel Callejo

24

Щоб отримати логіку ігнорування вставки, ви можете зробити щось на кшталт нижче. Я виявив, що просто вставка з обраного оператора буквальних значень найкраще працювала, тоді ви можете замаскувати повторювані ключі за допомогою пункту 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)
)

Що робити, якщо tmp містить повторюваний рядок, що може статися?
Henley Chiu

Ви завжди можете вибрати за допомогою ключового слова.
Кейо

5
Так само, як і FYI, фокус "БОГО НІМАЄ" не працює в декількох транзакціях, оскільки різні транзакції не можуть бачити нещодавно додані дані з інших транзакцій.
Дейв Йохансен

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

Який вплив мають кілька операцій, які намагаються зробити одне і те ж? Чи можливо, що між виконанням, де не існує, і вставкою, що виконує якусь іншу транзакцію, вставляється рядок? І якщо Postgres може це запобігти, то чи не введено postgres точку синхронізації для всіх цих транзакцій, коли вони потрапляють на це?
Καrτhικ

Це не працює з кількома транзакціями, оскільки щойно додані дані не видно для інших транзакцій.
Дейв Йохансен

12

Схоже, PostgreSQL підтримує об'єкт схеми, який називається правилом .

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

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

Я сам цього не пробував, тому не можу говорити з досвіду чи пропонувати приклад.


1
якщо я добре зрозумів, ці правила - це тригери, які виконуються щоразу, коли викликається оператор. що робити, якщо я хочу застосувати правило лише до одного запиту? я повинен створити правило, а потім негайно його знищити? (як щодо умов перегонів?)
gpilotino

3
Так, у мене були б ті ж запитання. Механізм правил - це найближче, що я міг знайти у PostgreSQL до INSERT IGNORE MySQL або ON DUPLICATE KEY UPDATE. Якщо ми шукаємо "postgresql за оновленням дублікатів ключів", ви знайдете інших людей, які рекомендують механізм правила, навіть якщо правило застосовується до будь-якого INSERT, а не лише на спеціальній основі.
Білл Карвін

4
PostgreSQL підтримує транзакційний DDL, що означає, що якщо ви створите правило і впустите його в рамках однієї транзакції, правило ніколи не було б видно за межами (і, отже, ніколи не матиме жодного ефекту поза) транзакції.
cdhowie

6

Як згадував @hanmari у своєму коментарі. при вставці в таблиці постгресів, конфлікт (..) нічого не робити - це найкращий код, який не можна використовувати для вставки дублікатів даних.

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

Рядок коду ON CONFLICT дозволить оператору вставлення все ще вставляти рядки даних. Код запиту та значень є прикладом вставленої дати з Excel у таблицю db postgres. У таблицю постгресів я додаю обмеження, якими я користуюся, щоб переконатися, що поле ідентичності унікальне. Замість того, щоб виконувати видалення на однакових рядках даних, я додаю рядок коду sql, який переномериє стовпчик ідентифікатора, починаючи з 1. Приклад:

q = 'ALTER id_column serial RESTART WITH 1'

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


це не працює на складених унікальних показниках!
Nulik

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, для входу та виходу значно дорожчий, ніж блок без цього. Тому не використовуйте EXCEPTION без потреби.


1

Налаштовуючи, ви завжди можете видалити рядок перед вставкою. Видалення рядка, який не існує, не викликає помилки, тому його безпечно пропускають.


2
Цей підхід буде досить схильний до дивних умов гонки, я б не рекомендував його ...
Стівен Шланскер

1
+1 Це легко і загально. Якщо використовувати обережно, це насправді може бути простим рішенням.
Wouter van Nifterick

1
Він також не працюватиме, коли існуючі дані були змінені після вставки (але не на повторному ключі) і ми хочемо тримати оновлення. Це сценарій, коли є сценарії SQL, написані для декількох дещо різних систем, як-от db-оновлення, що працюють на виробництві, QA, розробниках і тестових системах.
Hanno Fietz

1
Зовнішній ключ може бути проблемою, якщо створити їх за допомогою DEFERRABLE INITIALLY DEFERREDпрапорів.
темто

-1

Для сценаріїв імпорту даних певним чином замінити "ЯКЩО НЕ Є" існує дещо незручне формулювання:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.