sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan inkluderes ekskluderede rækker i RETURNING from INSERT ... ON CONFLICT

Fejlen får du:

ON CONFLICT DO UPDATE-kommandoen kan ikke påvirke rækken en anden gang

angiver, at du forsøger at forskyde den samme række mere end én gang i en enkelt kommando. Med andre ord:du har duper på (name, url, email) i din VALUES liste. Fold dubletter (hvis det er en mulighed), og det burde virke. Men du bliver nødt til at beslutte, hvilken række du vil vælge fra hvert sæt duper.

INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM  (
   VALUES
   ('blah', 'blah', 'blah', 'blah', 'blah')
   -- ... more
   ) v(created, modified, name, url, email)  -- match column list
ON     CONFLICT (name, url, email) DO UPDATE
SET    url = feeds_person.url
RETURNING id;

Da vi bruger en fritstående VALUES udtryk nu, skal du tilføje eksplicitte typecasts for ikke-standardtyper. Ligesom:

VALUES
    (timestamptz '2016-03-12 02:47:56+01'
   , timestamptz '2016-03-12 02:47:56+01'
   , 'n3', 'u3', 'e3')
   ...

Din timestamptz kolonner har brug for en eksplicit type cast, mens strengtyperne kan fungere med standard text . (Du kan stadig caste til varchar(n) med det samme.)

Der er måder at bestemme, hvilken række der skal vælges fra hvert sæt duper:

  • Vælg første række i hver GROUP BY-gruppe?

Du har ret, der er (i øjeblikket) ingen måde at blive ekskluderet rækker i RETURNING klausul. Jeg citerer Postgres Wiki:

Bemærk, at RETURNING synliggør ikke "EXCLUDED.* " alias fra UPDATE (kun den generiske "TARGET.* " alias er synligt der). At gøre det menes at skabe irriterende tvetydighed for de simple, almindelige sager [30] til ringe eller ingen fordel. På et tidspunkt i fremtiden kan vi forfølge en måde at afsløre, hvisRETURNING -Projicerede tuples blev indsat og opdateret, men dette behøver sandsynligvis ikke at indgå i den første forpligtede iteration af funktionen [31].

Men , bør du ikke opdatere rækker, der ikke formodes at blive opdateret. Tomme opdateringer er næsten lige så dyre som almindelige opdateringer - og kan have utilsigtede bivirkninger. Du behøver strengt taget ikke UPSERT til at begynde med, din sag ligner mere "SELECT eller INSERT". Relateret:

  • Er SELECT eller INSERT i en funktion, der er tilbøjelig til løbsforhold?

En en renere måde at indsætte et sæt rækker på ville være med datamodificerende CTE'er:

WITH val AS (
   SELECT DISTINCT ON (name, url, email) *
   FROM  (
      VALUES 
      (timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
    , ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
      -- more (type cast only needed in 1st row)
      ) v(created, modified, name, url, email)
   )
, ins AS (
   INSERT INTO feeds_person (created, modified, name, url, email)
   SELECT created, modified, name, url, email FROM val
   ON     CONFLICT (name, url, email) DO NOTHING
   RETURNING id, name, url, email
   )
SELECT 'inserted' AS how, id FROM ins  -- inserted
UNION  ALL
SELECT 'selected' AS how, f.id         -- not inserted
FROM   val v
JOIN   feeds_person f USING (name, url, email);

Den ekstra kompleksitet burde betale for store borde, hvor INSERT er reglen og SELECT undtagelsen.

Oprindeligt havde jeg tilføjet en NOT EXISTS prædikat på den sidste SELECT for at forhindre dubletter i resultatet. Men det var overflødigt. Alle CTE'er i en enkelt forespørgsel ser de samme øjebliksbilleder af tabeller. Sættet returnerede med ON CONFLICT (name, url, email) DO NOTHING udelukker gensidigt det sæt, der returneres efter INNER JOIN på de samme kolonner.

Desværre åbner dette også et lille vindue for en løbstilstand . Hvis ...

  • en samtidig transaktion indsætter modstridende rækker
  • har ikke forpligtet sig endnu
  • men forpligter sig til sidst

... nogle rækker kan gå tabt.

Du kan bare INSERT .. ON CONFLICT DO NOTHING , efterfulgt af en separat SELECT forespørgsel for alle rækker - inden for samme transaktion for at overvinde dette. Hvilket igen åbner endnu et lille vindue for en løbstilstand hvis samtidige transaktioner kan begå skrivninger til tabellen mellem INSERT og SELECT (i standard READ COMMITTED isolationsniveau). Kan undgås med REPEATABLE READ transaktionsisolering (eller strengere). Eller med en (muligvis dyr eller endda uacceptabel) skrivelås på hele bordet. Du kan få enhver adfærd, du har brug for, men der kan være en pris at betale.

Relateret:

  • Hvordan bruger man RETURNING med ON CONFLICT i PostgreSQL?
  • Returner rækker fra INSERT med ON CONFLICT uden at skulle opdatere



  1. Postgres-forbindelsen er blevet lukket fejl i Spring Boot

  2. 10 tidsbesparende genveje i Microsoft Access

  3. Fremmednøglebegrænsning kan forårsage cyklusser eller flere kaskadestier?

  4. Ti tips til at gå i produktion med PostgreSQL