sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur man inkluderar exkluderade rader i ÅTERKOMMANDE från INFOGA ... VID KONFLIKT

Felet du får:

ON CONFLICT DO UPDATE-kommandot kan inte påverka raden en andra gång

indikerar att du försöker flytta upp samma rad mer än en gång i ett enda kommando. Med andra ord:du har duper på (name, url, email) i din VALUES lista. Vik dubbletter (om det är ett alternativ) och det borde fungera. Men du måste bestämma vilken rad du ska välja från varje uppsättning 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;

Eftersom vi använder en fristående VALUES expression nu måste du lägga till explicita typcasts för icke-standardtyper. Gilla:

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

Din timestamptz kolumner behöver en explicit typavgjutning, medan strängtyperna kan fungera med standard text . (Du kan fortfarande casta till varchar(n) direkt.)

Det finns sätt att bestämma vilken rad du ska välja från varje uppsättning duper:

  • Välj första raden i varje GROUP BY-grupp?

Du har rätt, det finns (för närvarande) inget sätt att bli utesluten rader i RETURNING klausul. Jag citerar Postgres Wiki:

Observera att RETURNING visar inte "EXCLUDED.* " alias från UPDATE (bara det generiska "TARGET.* " alias är synligt där). Att göra det tros skapa irriterande tvetydighet för de enkla, vanliga fallen [30] till liten eller ingen nytta. Någon gång i framtiden kan vi komma att försöka avslöja omRETURNING -Projicerade tupler infogades och uppdaterades, men detta behöver antagligen inte komma in i den första begångna iterationen av funktionen [31].

Men , bör du inte uppdatera rader som inte är tänkta att uppdateras. Tomma uppdateringar är nästan lika dyra som vanliga uppdateringar - och kan ha oavsiktliga biverkningar. Du behöver strikt inte UPSERT till att börja med, ditt fall ser mer ut som "SELECT eller INSERT". Relaterat:

  • Är SELECT eller INSERT i en funktion utsatt för tävlingsförhållanden?

En renare sätt att infoga en uppsättning rader skulle vara med datamodifierande 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 extra komplexiteten borde betala för stora tabeller där INSERT är regeln och SELECT undantaget.

Ursprungligen hade jag lagt till en NOT EXISTS predikat på den sista SELECT för att förhindra dubbletter i resultatet. Men det var överflödigt. Alla CTE i en enda fråga ser samma ögonblicksbilder av tabeller. Uppsättningen returnerade med ON CONFLICT (name, url, email) DO NOTHING är ömsesidigt uteslutande för uppsättningen som returneras efter INNER JOIN på samma kolumner.

Tyvärr öppnar detta också ett litet fönster för tävlingsförhållanden . Om ...

  • en samtidig transaktion infogar motstridiga rader
  • har inte åtagit sig ännu
  • men förpliktar sig så småningom

... vissa rader kan gå förlorade.

Du kanske bara INSERT .. ON CONFLICT DO NOTHING , följt av en separat SELECT fråga för alla rader - inom samma transaktion för att övervinna detta. Vilket i sin tur öppnar ytterligare ett litet fönster för ett tävlingsförhållande om samtidiga transaktioner kan begå skrivningar till tabellen mellan INSERT och SELECT (som standard READ COMMITTED isoleringsnivå). Kan undvikas med REPEATABLE READ transaktionsisolering (eller strängare). Eller med ett (möjligen dyrt eller till och med oacceptabelt) skrivlås på hela bordet. Du kan få vilket beteende du behöver, men det kan finnas ett pris att betala.

Relaterat:

  • Hur använder man RETURNING med ON CONFLICT i PostgreSQL?
  • Returnera rader från INSERT med ON CONFLICT utan att behöva uppdatera



  1. hur man använder XMLImporter och FndXdfCmp i Oracle EBS

  2. Neo4j - Skapa ett index med Cypher

  3. SQL Dev 4.2 Topp SQL

  4. Hur man migrerar SQL Server-jobb från en SQL Server-instans till en annan