Postgres 9.5 implementerade UPSERT . Se nedan.
Postgres 9.4 eller äldre
Det här är ett knepigt problem. Du stöter på denna begränsning (per dokumentation):
I en
VALUESlista som visas på den översta nivån av enINSERT, kan ett uttryck ersättas medDEFAULTför att indikera att målkolumnens standardvärde ska infogas.DEFAULTkan inte användas närVALUESdyker upp i andra sammanhang.
Djärv betoning min. Standardvärden definieras inte utan en tabell att infoga i. Så det finns ingen direkt lösning på din fråga, men det finns ett antal möjliga alternativa vägar, beroende på exakta krav .
Hämta standardvärden från systemkatalogen?
Du kunde hämta dem från systemkatalogen pg_attrdef som @Patrick kommenterade eller från information_schema.columns . Kompletta instruktioner här:
- Hämta standardvärdena för tabellkolumner i Postgres?
Men då fortfarande har bara en lista med rader med en textrepresentation av uttrycket för att laga standardvärdet. Du skulle behöva bygga och utföra uttalanden dynamiskt för att få värden att arbeta med. Tråkigt och rörigt. Istället kan vi låta inbyggd Postgres-funktionalitet göra det åt oss :
Enkel genväg
Infoga en dummy-rad och få den tillbaka för att använda genererade standardvärden:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
Problem/lösningens omfattning
- Detta fungerar garanterat endast för
STABLEellerIMMUTABLEstandarduttryck . MestVOLATILEfunktioner kommer att fungera lika bra, men det finns inga garantier.current_timestampfamilj av funktioner kvalificeras som stabila, eftersom deras värden inte ändras inom en transaktion.
Särskilt har detta bieffekter påserialkolumner (eller andra standardinställningar som ritar från en sekvens). Men det borde inte vara ett problem, eftersom du normalt inte skriver tillserialkolumner direkt. De ska inte listas iINSERTuttalanden överhuvudtaget.
Återstående fel förserialkolumner:sekvensen flyttas fortfarande fram av det enda anropet för att få en standardrad, vilket skapar ett gap i numreringen. Återigen, det borde inte vara ett problem, eftersom luckor allmänt är att förvänta sig iserialkolumner.
Ytterligare två problem kan lösas:
-
Om du har definierade kolumner
NOT NULL, måste du infoga dummyvärden och ersätta medNULLi resultatet. -
Vi vill faktiskt inte infoga attrappraden . Vi skulle kunna ta bort senare (i samma transaktion), men det kan ha fler biverkningar, som triggers
ON DELETE. Det finns ett bättre sätt:
Undvik dummy row
Klona en tillfällig tabell inklusive kolumnstandarder och infoga 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 *;
...
Samma resultat, färre biverkningar. Eftersom standarduttryck kopieras ordagrant, drar klonen från samma sekvenser om några. Men andra biverkningar från den oönskade raden eller triggers undviks helt.
Tack till Igor för idén:
- Postgresql, välj en "falsk" rad
Ta bort NOT NULL begränsningar
Du måste ange dummyvärden för NOT NULL kolumner, eftersom (per dokumentation):
Begränsningar som inte är null kopieras alltid till den nya tabellen.
Antingen passar de i INSERT uttalande eller (bättre) eliminera begränsningarna:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
Det finns ett snabbt och smutsigt sätt med superanvändarbehörigheter:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
Det är bara en tillfällig tabell utan data och inget annat syfte, och den tas bort i slutet av transaktionen. Så genvägen är frestande. Ändå är grundregeln:manipulera aldrig systemkataloger direkt.
Så låt oss titta på ett rent sätt :Automatisera med dynamisk SQL i en DO påstående. Du behöver bara de vanliga behörigheterna du har garanterat haft sedan samma roll skapade temptabellen.
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$$
Mycket renare och fortfarande väldigt snabb. Var försiktig med dynamiska kommandon och var försiktig med SQL-injektion. Detta uttalande är säkert. Jag har lagt upp flera relaterade svar med mer förklaring.
Allmän lösning (9.4 och ä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 bara LOCK om du har samtidiga transaktioner som försöker skriva till samma tabell.
Som begärt ersätter detta endast NULL-värden i kolumnen legacy i inmatningsraderna för INSERT fall. Kan enkelt utökas till att fungera för andra kolumner eller i UPDATE fall också. Du kan till exempel UPDATE villkorligt också:endast om inmatningsvärdet är NOT NULL . Jag lade till en kommenterad rad i UPDATE ovan.
Bortsett från:Du behöver inte casta värden i valfri rad men de första i en VALUES uttryck, eftersom typer härrör från den första rad.
Postgres 9.5
implementerar UPSERT med INSERT .. ON CONFLICT .. DO NOTHING | UPDATE . Detta förenklar till stor del operationen:
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 bifoga VALUES sats till INSERT direkt, vilket tillåter DEFAULT nyckelord. Vid unika överträdelser på (id) , Postgres uppdaterar istället. Vi kan använda exkluderade rader i UPDATE . Manualen:
SETochWHEREsatser iON CONFLICT DO UPDATEha tillgång till den befintliga raden med tabellens namn (eller ett alias) och till rader som är föreslagna för infogning med den speciellaexcludedbord.
Och:
Observera att effekterna av alla per rad
BEFORE INSERTtriggers återspeglas i exkluderade värden, eftersom dessa effekter kan ha bidragit till att raden uteslutits från infogning.
Återstående hörnfodral
Du har olika alternativ för UPDATE :Du kan ...
- ... inte uppdatera alls:lägg till en
WHEREklausul tillUPDATEatt bara skriva till markerade rader. - ... uppdatera bara valda kolumner.
- ... uppdatera bara om kolumnen för närvarande är NULL:
COALESCE(l.legacy, EXCLUDED.legacy) - ... uppdatera bara om det nya värdet är
NOT NULL:COALESCE(EXCLUDED.legacy, l.legacy)
Men det finns inget sätt att urskilja DEFAULT värden och värden som faktiskt tillhandahålls i INSERT . Endast resulterande EXCLUDED rader är synliga. Om du behöver distinktionen, fall tillbaka till den tidigare lösningen, där du har båda till vårt förfogande.