UPDATE
syntax kräver för att uttryckligen namnge målkolumner. Möjliga skäl att undvika det:
- Du har många kolumner och vill bara förkorta syntaxen.
- Du vet inte kolumnnamn förutom de unika kolumnerna.
"All columns"
måste betyda "alla kolumner i måltabellen" (eller åtminstone "ledande kolumner i tabellen" ) i matchande ordning och matchande datatyp. Annars måste du ändå tillhandahålla en lista med målkolumnnamn.
Testtabell:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
1. DELETE
&INSERT
i en enda fråga istället
Utan att känna till några kolumnnamn förutom id
.
Fungerar endast för "alla kolumner i måltabellen" . Även om syntaxen till och med fungerar för en ledande delmängd, skulle överflödiga kolumner i måltabellen återställas till NULL med DELETE
och INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) behövs för att undvika samtidighets-/låsningsproblem under samtidig skrivbelastning, och bara för att det inte finns något allmänt sätt att låsa rader som ännu inte finns i Postgres (värdelåsning ).
Dina speciella krav påverkar bara UPDATE
del. Eventuella komplikationer gäller inte där existerar rader påverkas. De är ordentligt låsta. För att förenkla något mer kan du minska ditt ärende till DELETE
och 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;
I Postgres MVCC-modellen, en UPDATE
är i stort sett detsamma som DELETE
och INSERT
i alla fall (förutom vissa hörnfall med samtidighet, HETA uppdateringar och stora kolumnvärden lagrade utanför linjen). Eftersom du ändå vill ersätta alla rader, ta bara bort motstridiga rader före INSERT
. Raderade rader förblir låsta tills transaktionen genomförs. INSERT
kan bara hitta motstridiga rader för tidigare icke-existerande nyckelvärden om en samtidig transaktion råkar infoga dem samtidigt (efter DELETE
, men före INSERT
).
Du skulle förlora ytterligare kolumnvärden för berörda rader i detta speciella fall. Inget undantag togs upp. Men om konkurrerande frågor har samma prioritet är det knappast något problem:den andra frågan vann för några rader. Dessutom, om den andra frågan är en liknande UPSERT, är dess alternativ att vänta på att den här transaktionen genomförs och sedan uppdateras direkt. "Att vinna" kan vara en pyrrhusseger.
Om "tomma uppdateringar":
- Hur väljer jag (eller kan jag) DISTINCT på flera kolumner?
Nej, min fråga måste vinna!
OK, du bad om det:
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;
Hur?
- Första CTE
data
ger bara data. Kan vara ett bord istället. - Den andra CTE
ups
:UPSERT. Rader med konfliktid
inte ändras, utan också låsta . - Den tredje CTE
del
tar bort motstridiga rader. De förblir låsta. - Den fjärde CTE
ins
infogar hela rader . Endast tillåtet för samma transaktion - Det sista VALET är endast för demon för att visa vad som hände.
För att leta efter tomma uppdateringar testa (före och efter) med:
SELECT ctid, * FROM tbl; -- did the ctid change?
Den (kommenterade) kontrollera för eventuella ändringar i raden AND t <> d
fungerar även med NULL-värden eftersom vi jämför två inskrivna radvärden enligt manualen:
två NULL-fältvärden anses vara lika, och en NULL anses vara större än en icke-NULL
2. Dynamisk SQL
Detta fungerar även för en delmängd av ledande kolumner och bevarar befintliga värden.
Tricket är att låta Postgres bygga frågesträngen med kolumnnamn från systemkatalogerna dynamiskt och sedan köra den.
Se relaterade svar för kod:
-
Uppdatera flera kolumner i en triggerfunktion i plpgsql
-
Massuppdatering av alla kolumner
-
SQL uppdatera fält i en tabell från fält i en annan