Postgres 9.5 implementeret UPSERT
. Se nedenfor.
Postgres 9.4 eller ældre
Dette er et vanskeligt problem. Du løber ind i denne begrænsning (pr. dokumentation):
I en VALUES
liste, der vises på det øverste niveau af en INSERT
, kan et udtryk erstattes af DEFAULT
for at angive, at destinationskolonnens standardværdi skal indsættes. DEFAULT
kan ikke bruges, nårVALUES
optræder i andre sammenhænge.
Fed understregning min. Standardværdier er ikke defineret uden en tabel at indsætte i. Så der er ingen direkte løsning på dit spørgsmål, men der er en række mulige alternative ruter, afhængigt af præcise krav .
Hent standardværdier fra systemkataloget?
Du kunne hent dem fra systemkataloget pg_attrdef
som @Patrick kommenterede eller fra information_schema.columns
. Fuldstændig instruktioner her:
- Få standardværdierne for tabelkolonner i Postgres?
Men så stadig har kun en liste over rækker med en tekstrepræsentation af udtrykket for at tilberede standardværdien. Du ville være nødt til at bygge og udføre sætninger dynamisk for at få værdier at arbejde med. Kedeligt og rodet. I stedet kan vi lade indbygget Postgres-funktionalitet gøre det for os :
Simpel genvej
Indsæt en dummy-række og få den returneret til at bruge genererede standardindstillinger:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
Problemer/løsningens omfang
- Dette er kun garanteret at virke for
STABLE
ellerIMMUTABLE
standardudtryk . MestVOLATILE
funktioner vil fungere lige så godt, men der er ingen garantier.current_timestamp
familie af funktioner kvalificeres som stabile, da deres værdier ikke ændres inden for en transaktion.
Dette har især bivirkninger påserial
kolonner (eller andre standardindstillinger, der tegner fra en sekvens). Men det burde ikke være et problem, for du skriver normalt ikke tilserial
kolonner direkte. Disse bør ikke være opført iINSERT
udsagn overhovedet.
Resterende fejl forserial
kolonner:sekvensen er stadig avanceret af det enkelte kald for at få en standardrække, hvilket giver et hul i nummereringen. Igen, det burde ikke være et problem, for huller kan generelt forventes iserial
kolonner.
Yderligere to problemer kan løses:
-
Hvis du har defineret kolonner
NOT NULL
, skal du indsætte dummy-værdier og erstatte medNULL
i resultatet. -
Vi ønsker faktisk ikke at indsætte dummy-rækken . Vi kunne slette senere (i samme transaktion), men det kan have flere bivirkninger, såsom triggere
ON DELETE
. Der er en bedre måde:
Undgå dummy row
Klon en midlertidig tabel inklusive kolonnestandarder og indsæt i det :
BEGIN;
CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
ON COMMIT DROP; -- drop at end of transaction
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
...
Samme resultat, færre bivirkninger. Da standardudtryk kopieres ordret, trækker klonen fra de samme sekvenser, hvis nogen. Men andre bivirkninger fra den uønskede række eller triggere undgås fuldstændigt.
Tak til Igor for ideen:
- Postgresql, vælg en "falsk" række
Fjern NOT NULL
begrænsninger
Du skal angive dummy-værdier for NOT NULL
kolonner, fordi (pr. dokumentation):
Ikke-nul-begrænsninger kopieres altid til den nye tabel.
Enten rumme dem i INSERT
sætning eller (bedre) eliminere begrænsningerne:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
Der er en hurtig og beskidt måde med superbrugerrettigheder:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
Det er blot en midlertidig tabel uden data og intet andet formål, og den er droppet ved slutningen af transaktionen. Så genvejen er fristende. Alligevel er den grundlæggende regel:manipuler aldrig med systemkataloger direkte.
Så lad os se på en ren måde :Automatiser med dynamisk SQL i en DO
udmelding. Du skal bare bruge de almindelige privilegier du er garanteret at have siden den samme rolle skabte vikartabellen.
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$
Meget renere og stadig meget hurtig. Udfør forsigtighed med dynamiske kommandoer og vær på vagt over for SQL-injektion. Denne erklæring er sikker. Jeg har postet flere relaterede svar med mere forklaring.
Generel løsning (9.4 og ældre)
BEGIN;
CREATE TEMP TABLE tmp_playlist_items
(LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$;
LOCK TABLE playlist_items IN EXCLUSIVE MODE; -- forbid concurrent writes
WITH default_row AS (
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
)
, new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
VALUES
(651, 21, 30012, 'a', 30, 1, FALSE)
, (NULL, 21, 1, 'b', 34, 2, NULL)
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
)
, upsert AS ( -- *not* replacing existing values in UPDATE (?)
UPDATE playlist_items m
SET ( playlist, item, group_name, duration, sort, legacy)
= (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
-- ..., COALESCE(n.legacy, m.legacy) -- see below
FROM new_values n
WHERE n.id = m.id
RETURNING m.id
)
INSERT INTO playlist_items
(playlist, item, group_name, duration, sort, legacy)
SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
, COALESCE(n.legacy, d.legacy)
FROM new_values n, default_row d -- single row can be cross-joined
WHERE NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
RETURNING id;
COMMIT;
Du behøver kun LOCK
hvis du har samtidige transaktioner, der prøver at skrive til den samme tabel.
Som anmodet erstatter dette kun NULL-værdier i kolonnen legacy
i inputrækkerne for INSERT
sag. Kan nemt udvides til at fungere for andre kolonner eller i UPDATE
sagen også. For eksempel kan du UPDATE
også betinget:kun hvis inputværdien er NOT NULL
. Jeg tilføjede en kommenteret linje til UPDATE
ovenfor.
Til side:Du behøver ikke at caste værdier i enhver række, men den første i en VALUES
udtryk, da typer er afledt af den første række.
Postgres 9.5
implementerer UPSERT med INSERT .. ON CONFLICT .. DO NOTHING | UPDATE
. Dette forenkler i høj grad betjeningen:
INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
, (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT) -- !
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
ON CONFLICT (id) DO UPDATE
SET (playlist, item, group_name, duration, sort, legacy)
= (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
, EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
-- (..., COALESCE(l.legacy, EXCLUDED.legacy)) -- see below
RETURNING m.id;
Vi kan vedhæfte VALUES
klausul til INSERT
direkte, hvilket tillader DEFAULT
søgeord. I tilfælde af unikke overtrædelser på (id)
, Postgres opdaterer i stedet. Vi kan bruge ekskluderede rækker i UPDATE
. Manualen:
SET
og WHERE
klausuler i ON CONFLICT DO UPDATE
har adgang til den eksisterende række ved hjælp af tabellens navn (eller et alias), og til rækker, der er foreslået til indsættelse ved hjælp af den særlige excluded
tabel.
Og:
Bemærk, at effekterne af alle pr. række BEFORE INSERT
triggere afspejles i ekskluderede værdier, da disse effekter kan have bidraget til, at rækken er blevet udelukket fra indsættelse.
Resterende hjørnekasse
Du har forskellige muligheder for UPDATE
:Du kan ...
- ... slet ikke opdatere:tilføj en
WHERE
klausul tilUPDATE
kun at skrive til udvalgte rækker. - ... opdater kun valgte kolonner.
- ... opdater kun, hvis kolonnen i øjeblikket er NULL:
COALESCE(l.legacy, EXCLUDED.legacy)
- ... opdater kun, hvis den nye værdi er
NOT NULL
:COALESCE(EXCLUDED.legacy, l.legacy)
Men der er ingen måde at skelne DEFAULT
værdier og værdier, der faktisk er angivet i INSERT
. Kun resulterende EXCLUDED
rækker er synlige. Hvis du har brug for udmærkelsen, så fald tilbage til den tidligere løsning, hvor du har begge dele til vores rådighed.