PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Generuj wartości DOMYŚLNE w CTE UPSERT przy użyciu PostgreSQL 9.3

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 STABLE lub IMMUTABLE wyrażenia domyślne . Większość VOLATILE funkcje będą działać równie dobrze, ale nie ma żadnych gwarancji. current_timestamp rodzina funkcji kwalifikuje się jako stabilna, ponieważ ich wartości nie zmieniają się w ramach transakcji.
    W szczególności ma to skutki uboczne w przypadku serial kolumny (lub dowolne inne wartości domyślne na podstawie sekwencji). Ale to nie powinno stanowić problemu, ponieważ zwykle nie piszesz do serial kolumny bezpośrednio. Nie powinny być wymienione w INSERT w ogóle.
    Pozostała luka dla serial kolumny: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ć w serial kolumny.

Można rozwiązać jeszcze dwa problemy:

  • Jeśli masz zdefiniowane kolumny NOT NULL , musisz wstawić fałszywe wartości i zastąpić je NULL w 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 WHERE klauzula do UPDATE pisać 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.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak zwrócić tablicę jsonb i tablicę obiektów z moich danych?

  2. Jak używać pętli for SQL do wstawiania wierszy do bazy danych?

  3. Jak zmapować tablicę PostgreSQL za pomocą Hibernate?

  4. ScaleGrid PostgreSQL w infrastrukturze chmury VMware

  5. Porównanie wydajności i cen PostgreSQL DigitalOcean — ScaleGrid i zarządzane bazy danych DigitalOcean