Postgres 9.5 zaimplementowany UPSERT . Zobacz poniżej.
Postgres 9.4 lub starszy
To trudny problem. Natrafiasz na to ograniczenie (zgodnie z dokumentacją):
W VALUES lista pojawiająca się na najwyższym poziomie INSERT , wyrażenie można zastąpić przez DEFAULT aby wskazać, że należy wstawić domyślną wartość kolumny docelowej. DEFAULT nie można użyć, gdyVALUES pojawia się w innych kontekstach.
Moje odważne podkreślenie. Wartości domyślne nie są definiowane bez tabeli do wstawienia. Więc nie ma bezpośredniego rozwiązanie Twojego pytania, ale istnieje wiele możliwych tras alternatywnych, w zależności od dokładnych wymagań .
Pobrać wartości domyślne z katalogu systemowego?
możesz pobierz je z katalogu systemowego pg_attrdef jak skomentował @Patrick lub z information_schema.columns . Pełne instrukcje tutaj:
- Pobrać domyślne wartości kolumn tabeli w Postgresie?
Ale wtedy nadal mieć tylko listę wierszy z tekstową reprezentacją wyrażenia gotującego wartość domyślną. Trzeba by dynamicznie budować i wykonywać instrukcje, aby uzyskać wartości do pracy. Żmudne i niechlujne. Zamiast tego możemy pozwolić wbudowanej funkcjonalności Postgres zrobić to za nas :
Prosty skrót
Wstaw fikcyjny wiersz i zwróć go, aby używał wygenerowanych wartości domyślnych:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
Problemy / zakres rozwiązania
- Gwarantuje się, że zadziała tylko w przypadku
STABLElubIMMUTABLEwyrażenia domyślne . WiększośćVOLATILEfunkcje będą działać równie dobrze, ale nie ma żadnych gwarancji.current_timestamprodzina funkcji kwalifikuje się jako stabilna, ponieważ ich wartości nie zmieniają się w ramach transakcji.
W szczególności ma to skutki uboczne w przypadkuserialkolumny (lub dowolne inne wartości domyślne na podstawie sekwencji). Ale to nie powinno stanowić problemu, ponieważ zwykle nie piszesz doserialkolumny bezpośrednio. Nie powinny być wymienione wINSERTw ogóle.
Pozostała luka dlaserialkolumny:sekwencja jest nadal przesuwana przez pojedyncze wywołanie, aby uzyskać domyślny wiersz, tworząc lukę w numeracji. Ponownie, nie powinno to stanowić problemu, ponieważ luki na ogół należy się spodziewać wserialkolumny.
Można rozwiązać jeszcze dwa problemy:
-
Jeśli masz zdefiniowane kolumny
NOT NULL, musisz wstawić fałszywe wartości i zastąpić jeNULLw wyniku. -
W rzeczywistości nie chcemy wstawiać fałszywego wiersza . Moglibyśmy usunąć później (w tej samej transakcji), ale może to mieć więcej skutków ubocznych, takich jak wyzwalacze
ON DELETE. Jest lepszy sposób:
Unikaj fałszywego rzędu
Sklonuj tablicę tymczasową w tym wartości domyślne kolumn i wstawiaj do tego :
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 *;
...
Ten sam wynik, mniej skutków ubocznych. Ponieważ wyrażenia domyślne są kopiowane dosłownie, klon rysuje z tych samych sekwencji, jeśli takie istnieją. Ale inne efekty uboczne z niechcianego rzędu lub wyzwalaczy są całkowicie unikane.
Podziękowania dla Igora za pomysł:
- Postgresql, wybierz „fałszywy” wiersz
Usuń NOT NULL ograniczenia
Musiałbyś podać fałszywe wartości dla NOT NULL kolumny, ponieważ (zgodnie z dokumentacją):
Ograniczenia typu not-null są zawsze kopiowane do nowej tabeli.
Albo pomieścić dla tych w INSERT oświadczenie lub (lepiej) wyeliminuj ograniczenia:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
Jest szybki i brudny sposób z uprawnieniami superużytkownika:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
Jest to po prostu tymczasowa tabela bez danych i bez innego celu, która jest usuwana na koniec transakcji. Więc skrót jest kuszący. Jednak podstawowa zasada brzmi:nigdy nie manipuluj bezpośrednio w katalogach systemowych.
Spójrzmy więc na czysty sposób :Automatyzacja za pomocą dynamicznego SQL w DO oświadczenie. Potrzebujesz tylko regularnych uprawnień masz gwarancję, że masz, ponieważ ta sama rola utworzyła tabelę tymczasową.
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$$
Dużo czyściej i nadal bardzo szybko. Zachowaj ostrożność za pomocą poleceń dynamicznych i uważaj na wstrzykiwanie SQL. To stwierdzenie jest bezpieczne. Wysłałem kilka powiązanych odpowiedzi z dodatkowymi wyjaśnieniami.
Rozwiązanie ogólne (9.4 i starsze)
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;
Potrzebujesz tylko LOCK jeśli masz jednoczesne transakcje, które próbują zapisywać do tej samej tabeli.
Zgodnie z żądaniem zastępuje to tylko wartości NULL w kolumnie legacy w wierszach wejściowych dla INSERT walizka. Można łatwo rozszerzyć do pracy z innymi kolumnami lub w UPDATE przypadku również. Na przykład możesz UPDATE również warunkowo:tylko jeśli wartość wejściowa to NOT NULL . Dodałem skomentowaną linię do UPDATE powyżej.
Na bok:nie musisz przesyłać wartości w dowolnym wierszu oprócz pierwszego w VALUES wyrażenie, ponieważ typy pochodzą od pierwszego wiersz.
Postgres 9.5
implementuje UPSERT z INSERT .. ON CONFLICT .. DO NOTHING | UPDATE . To znacznie upraszcza operację:
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;
Możemy dołączyć VALUES klauzula INSERT bezpośrednio, co pozwala na DEFAULT słowo kluczowe. W przypadku unikalnych naruszeń w (id) , zamiast tego aktualizacje Postgres. Możemy użyć wykluczonych wierszy w UPDATE . Instrukcja:
SET i WHERE klauzule w ON CONFLICT DO UPDATE mieć dostęp do istniejącego wiersza za pomocą nazwy tabeli (lub aliasu) oraz do wierszy proponowanych do wstawienia za pomocą specjalnego excluded tabela.
Oraz:
Zwróć uwagę, że efekty wszystkich na wiersz BEFORE INSERT wyzwalacze są odzwierciedlane w wartościach wykluczonych, ponieważ te efekty mogły przyczynić się do wykluczenia wiersza z wstawiania.
Pozostały przypadek narożny
Masz różne opcje UPDATE :Możesz...
- ... w ogóle nie aktualizuj:dodaj
WHEREklauzula doUPDATEpisać tylko do wybranych wierszy. - ... aktualizuj tylko wybrane kolumny.
- ... aktualizuj tylko, jeśli kolumna ma obecnie wartość NULL:
COALESCE(l.legacy, EXCLUDED.legacy) - ... aktualizuj tylko, jeśli nowa wartość to
NOT NULL:COALESCE(EXCLUDED.legacy, l.legacy)
Ale nie ma sposobu, aby rozpoznać DEFAULT wartości i wartości faktycznie podane w INSERT . Tylko wynikowe EXCLUDED widoczne są wiersze. Jeśli potrzebujesz rozróżnienia, wróć do poprzedniego rozwiązania, w którym masz oba do naszej dyspozycji.