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 актуализиране на полета на една таблица от полета на друга