PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да актуализирам всички колони с INSERT ... ON CONFLICT ...?

UPDATE синтаксис изисква за изрично именуване на целевите колони. Възможни причини да избегнете това:

  • Имате много колони и просто искате да съкратите синтаксиса.
  • Не знаете имена на колони, с изключение на уникалната(ите) колона(и).

"All columns" трябва да означава "всички колони на целевата таблица" (или поне „водещи колони на таблицата“ ) в съвпадащ ред и съвпадащ тип данни. В противен случай все пак ще трябва да предоставите списък с имена на целеви колони.

Тестова таблица:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');

1. DELETE &INSERT вместо това в единична заявка

Без да знаете имена на колони освен id .

Работи само за "всички колони на целевата таблица" . Докато синтаксисът работи дори за водещо подмножество, излишните колони в целевата таблица ще бъдат нулирани на NULL с DELETE и INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) е необходим, за да се избегнат проблеми с едновременността/заключването при едновременно натоварване при запис и само защото няма общ начин за заключване на все още несъществуващи редове в Postgres (заключване на стойности ).

Вашето специално изискване засяга само UPDATE част. Възможните усложнения не се прилагат, когато съществуват редовете са засегнати. Те са добре заключени. Като опростите още малко, можете да намалите вашия случай до DELETE и INSERT :

WITH data(id) AS (              -- Only 1st column gets explicit name!
   VALUES
      (1, 'foo_upd', 'a')       -- changed
    , (2, 'bar', 'b')           -- unchanged
    , (3, 'baz', 'c')           -- new
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data d
   WHERE  t.id = d.id
   -- AND    t <> d              -- optional, to avoid empty updates
   )                             -- only works for complete rows
INSERT INTO tbl AS t
TABLE  data                      -- short for: SELECT * FROM data
ON     CONFLICT (id) DO NOTHING
RETURNING t.id;

В модела на Postgres MVCC, UPDATE до голяма степен е същото като DELETE и INSERT така или иначе (с изключение на някои ъглови случаи с едновременност, ГОРЕЩИ актуализации и големи стойности на колони, съхранявани извън реда). Тъй като така или иначе искате да замените всички редове, просто премахнете конфликтните редове преди INSERT . Изтритите редове остават заключени, докато транзакцията не бъде ангажирана. INSERT може да открие конфликтни редове за несъществуващи преди това стойности на ключ, ако се случи едновременна транзакция да ги вмъкне едновременно (след DELETE , но преди INSERT ).

В този специален случай бихте загубили допълнителни стойности на колони за засегнатите редове. Не се прави изключение. Но ако конкурентните заявки имат равен приоритет, това едва ли е проблем:другата заявка печели за някои редове. Освен това, ако другата заявка е подобна на UPSERT, алтернативата й е да изчакате тази транзакция да се извърши и след това да се актуализира веднага. „Победата“ може да бъде пирова победа.

Относно „празни актуализации“:

  • Как да (или мога) да ИЗБЕРЯ DISTINCT за няколко колони?

Не, моята заявка трябва да спечели!

Добре, поискахте го:

WITH data(id) AS (                   -- Only 1st column gets explicit name!
   VALUES                            -- rest gets default names "column2", etc.
     (1, 'foo_upd', NULL)              -- changed
   , (2, 'bar', NULL)                  -- unchanged
   , (3, 'baz', NULL)                  -- new
   , (4, 'baz', NULL)                  -- new
   )
, ups AS (
   INSERT INTO tbl AS t
   TABLE  data                       -- short for: SELECT * FROM data
   ON     CONFLICT (id) DO UPDATE
   SET    id = t.id
   WHERE  false                      -- never executed, but locks the row!
   RETURNING t.id
   )
, del AS (
   DELETE FROM tbl AS t
   USING  data     d
   LEFT   JOIN ups u USING (id)
   WHERE  u.id IS NULL               -- not inserted !
   AND    t.id = d.id
   -- AND    t <> d                  -- avoid empty updates - only for full rows
   RETURNING t.id
   )
, ins AS (
   INSERT INTO tbl AS t
   SELECT *
   FROM   data
   JOIN   del USING (id)             -- conflict impossible!
   RETURNING id
   )
SELECT ARRAY(TABLE ups) AS inserted  -- with UPSERT
     , ARRAY(TABLE ins) AS updated   -- with DELETE & INSERT;

Как?

  • Първият CTE data просто предоставя данни. Вместо това може да е маса.
  • Вторият CTE ups :UPSERT. Редове с конфликтен id не се сменят, но изаключвати .
  • Третият CTE del изтрива конфликтни редове. Те остават заключени.
  • 4-ти CTE ins вмъква цели редове . Разрешено само за една и съща транзакция
  • Окончателният SELECT е само за демонстрацията, за да покаже какво се е случило.

За да проверите за празни актуализации, тествайте (преди и след) с:

SELECT ctid, * FROM tbl; -- did the ctid change?

Проверката (коментирана) за всякакви промени в реда AND t <> d работи дори с NULL стойности, защото сравняваме две въведени стойности на ред според ръководството:

две стойности на поле NULL се считат за равни, а NULL се счита за по-голямо от не-NULL

2. Динамичен SQL

Това работи и за подмножество от водещи колони, като запазва съществуващите стойности.

Номерът е да оставите Postgres да изгради динамично низа на заявката с имена на колони от системните каталози и след това да го изпълни.

Вижте свързаните отговори за код:

  • Актуализирайте множество колони в тригерна функция в plpgsql

  • Групова актуализация на всички колони

  • SQL актуализиране на полета на една таблица от полета на друга



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да възстановим една таблица от .sql postgresql архив?

  2. PostgreSQL Кръстосана заявка

  3. в postgres, можете ли да зададете форматирането по подразбиране за времева марка, по сесия или глобално?

  4. Използване на JSONB в PostgreSQL:Как ефективно да съхранявате и индексирате JSON данни в PostgreSQL

  5. как да поставим префикс на низ преди последователност, генерирана от postgresql?