UPDATE
syntaks kræver for eksplicit at navngive målkolonner. Mulige grunde til at undgå det:
- Du har mange kolonner og vil bare forkorte syntaksen.
- Du ved ikke kolonnenavne undtagen de unikke kolonne(r).
"All columns"
skal betyde "alle kolonner i måltabellen" (eller i det mindste "førende kolonner i tabellen" ) i matchende rækkefølge og matchende datatype. Ellers skal du alligevel angive en liste over målkolonnenavne.
Testtabel:
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 enkelt forespørgsel i stedet
Uden at kende nogen kolonnenavne undtagen id
.
Virker kun for "alle kolonner i måltabellen" . Mens syntaksen endda fungerer for et førende undersæt, vil overskydende kolonner i måltabellen blive nulstillet til NULL med DELETE
og INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) er nødvendig for at undgå samtidigheds-/låseproblemer under samtidig skrivebelastning, og kun fordi der ikke er nogen generel måde at låse ikke-endnu-eksisterende rækker i Postgres (værdilåsning ).
Dit særlige krav påvirker kun UPDATE
en del. Mulige komplikationer gælder ikke, hvor eksisterende rækker påvirkes. De er låst ordentligt. For at forenkle noget mere kan du reducere din sag til DELETE
og 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 er en UPDATE
er stort set det samme som DELETE
og INSERT
alligevel (bortset fra nogle hjørnesager med samtidighed, HOT opdateringer og store kolonneværdier gemt ude af linjen). Da du alligevel vil erstatte alle rækker, skal du bare fjerne modstridende rækker før INSERT
. Slettede rækker forbliver låst, indtil transaktionen er begået. INSERT
finder muligvis kun modstridende rækker for tidligere ikke-eksisterende nøgleværdier, hvis en samtidig transaktion tilfældigvis indsætter dem samtidigt (efter DELETE
, men før INSERT
).
Du vil miste yderligere kolonneværdier for berørte rækker i dette specielle tilfælde. Ingen undtagelse rejst. Men hvis konkurrerende forespørgsler har samme prioritet, er det næppe et problem:den anden forespørgsel vandt for nogle rækker. Desuden, hvis den anden forespørgsel er en lignende UPSERT, er dens alternativ at vente på, at denne transaktion forpligtes og derefter opdateres med det samme. "At vinde" kunne være en pyrrhussejr.
Om "tomme opdateringer":
- Hvordan (eller kan jeg) VÆLGE DISTINCT på flere kolonner?
Nej, min forespørgsel skal vinde!
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;
Hvordan?
- De 1. CTE
data
leverer blot data. Kunne være et bord i stedet for. - Den 2. CTE
ups
:UPSERT. Rækker med modstridendeid
er ikke ændret, men også låst . - Den 3. CTE
del
sletter modstridende rækker. De forbliver låst. - Den 4. CTE
ins
indsætter hele rækker . Kun tilladt for den samme transaktion - Det endelige SELECT er kun til demoen for at vise, hvad der skete.
For at tjekke for tomme opdateringer test (før og efter) med:
SELECT ctid, * FROM tbl; -- did the ctid change?
Den (kommenterede) check for eventuelle ændringer i rækken AND t <> d
fungerer selv med NULL-værdier, fordi vi sammenligner to indtastede rækkeværdier i henhold til manualen:
to NULL-feltværdier betragtes som ens, og en NULL betragtes som større end en ikke-NULL
2. Dynamisk SQL
Dette fungerer også for en undergruppe af ledende kolonner, og bevarer eksisterende værdier.
Tricket er at lade Postgres bygge forespørgselsstrengen med kolonnenavne fra systemkatalogerne dynamisk og derefter udføre den.
Se relaterede svar for kode:
-
Opdater flere kolonner i en triggerfunktion i plpgsql
-
Masseopdatering af alle kolonner
-
SQL-opdatering af felter i en tabel fra felter i en anden