sql >> Databáze >  >> RDS >> PostgreSQL

Jak aktualizovat všechny sloupce pomocí INSERT ... ON CONFLICT ...?

UPDATE syntaxe vyžaduje explicitně pojmenovat cílové sloupce. Možné důvody, proč se tomu vyhnout:

  • Máte mnoho sloupců a chcete pouze zkrátit syntaxi.
  • To nevíte názvy sloupců kromě jedinečných sloupců.

"All columns" musí znamenat "všechny sloupce cílové tabulky" (nebo alespoň "hlavní sloupce tabulky" ) v odpovídajícím pořadí a typu dat. Jinak byste stejně museli poskytnout seznam názvů cílových sloupců.

Testovací tabulka:

CREATE TABLE tbl (
   id    int PRIMARY KEY
 , text  text
 , extra text
);

INSERT INTO tbl AS t
VALUES (1, 'foo')
     , (2, 'bar');

1. DELETE &INSERT místo toho v jediném dotazu

Aniž byste znali jakékoli názvy sloupců kromě id .

Funguje pouze pro "všechny sloupce cílové tabulky" . I když syntaxe funguje i pro hlavní podmnožinu, nadbytečné sloupce v cílové tabulce by byly resetovány na NULL pomocí DELETE a INSERT .

UPSERT (INSERT ... ON CONFLICT ... ) je potřeba, aby se předešlo problémům se souběžností/uzamykáním při souběžném zatížení zápisu, a to pouze proto, že neexistuje žádný obecný způsob, jak zamknout dosud neexistující řádky v Postgresu (uzamykání hodnot ).

Váš speciální požadavek má vliv pouze na UPDATE část. Případné komplikace neplatí tam, kde existují řádky jsou ovlivněny. Ty jsou správně zamčené. Pro další zjednodušení můžete velikost písmen zmenšit na DELETE a 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;

V modelu Postgres MVCC UPDATE je z velké části stejný jako DELETE a INSERT každopádně (kromě některých rohových případů se souběžností, HOT aktualizací a velkých hodnot sloupců uložených mimo řádky). Protože přesto chcete nahradit všechny řádky, stačí odstranit konfliktní řádky před INSERT . Odstraněné řádky zůstanou uzamčeny, dokud není transakce potvrzena. INSERT může najít konfliktní řádky pro dříve neexistující hodnoty klíče pouze v případě, že je souběžná transakce vloží souběžně (za DELETE , ale před INSERT ).

V tomto speciálním případě byste přišli o další hodnoty sloupců pro ovlivněné řádky. Nebyla vznesena žádná výjimka. Ale pokud mají konkurenční dotazy stejnou prioritu, není to problém:u některých vyhrál druhý dotaz řádky. Také, pokud je druhý dotaz podobný UPSERT, jeho alternativou je počkat na potvrzení této transakce a poté se ihned aktualizuje. „Vítězství“ by mohlo být Pyrrhovo vítězství.

O „prázdných aktualizacích“:

  • Jak mohu (nebo mohu) SELECT DISTINCT na více sloupcích?

Ne, můj dotaz musí vyhrát!

Dobře, požádali jste o to:

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;

Jak?

  • První data CTE pouze poskytuje data. Místo toho může být stůl.
  • 2. CTE ups :UPSERT. Řádky s konfliktním id se nezmění, ale také zamknou .
  • 3. CTE del odstraní konfliktní řádky. Zůstanou uzamčeny.
  • Čtvrtý ins CTE vloží celé řádky . Povoleno pouze pro stejnou transakci
  • Poslední VÝBĚR slouží pouze k ukázce ukázky toho, co se stalo.

Pro kontrolu prázdných aktualizací test (před a po) pomocí:

SELECT ctid, * FROM tbl; -- did the ctid change?

Kontrola (komentována) na všechny změny v řádku AND t <> d funguje i s hodnotami NULL, protože porovnáváme dvě napsané hodnoty řádku podle manuálu:

dvě hodnoty pole NULL jsou považovány za stejné a hodnota NULL je považována za větší než hodnota bez NULL

2. Dynamický SQL

Toto funguje i pro podmnožinu úvodních sloupců, přičemž se zachovají stávající hodnoty.

Trik je nechat Postgres dynamicky sestavit řetězec dotazu s názvy sloupců ze systémových katalogů a poté jej spustit.

Viz související odpovědi pro kód:

  • Aktualizujte více sloupců ve funkci spouštění v plpgsql

  • Hromadná aktualizace všech sloupců

  • SQL aktualizace polí jedné tabulky z polí jiné tabulky



  1. Jak Cosd() funguje v PostgreSQL

  2. vytvořit vlastní funkci pro rozdíl v datech s výjimkou víkendů a svátků v Oracle SQL

  3. TSQL – Přidat sloupec do všech tabulek v databázi [Příklad kurzoru]

  4. Srovnání SQL Server int vs nvarchar z hlediska výkonu?