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

Jak zahrnout vyloučené řádky do RETURNING from INSERT ... ON CONFLICT

Zobrazí se chyba:

ON CONFLICT DO UPDATE příkaz nemůže ovlivnit řádek podruhé

označuje, že se pokoušíte vložit stejný řádek více než jednou jedním příkazem. Jinými slovy:máte podvody na (name, url, email) ve vašem VALUES seznam. Složte duplikáty (pokud je to možné) a mělo by to fungovat. Ale budete se muset rozhodnout, který řádek z každé sady podvodů vyberete.

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;

Protože používáme volně stojící VALUES výrazu, musíte přidat explicitní přetypování pro jiné než výchozí typy. Jako:

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

Vaše timestamptz sloupce vyžadují explicitní přetypování, zatímco typy řetězců mohou pracovat s výchozím text . (Stále můžete přenášet do varchar(n) hned.)

Existují způsoby, jak určit, který řádek vybrat z každé sady podvodů:

  • Vybrat první řádek v každé skupině GROUP BY?

Máte pravdu, není (momentálně) žádný způsob, jak se vyloučit řádků v RETURNING doložka. Cituji Postgres Wiki:

Všimněte si, že RETURNING nezobrazuje "EXCLUDED.* " alias z UPDATE (pouze obecný "TARGET.* " je tam viditelný alias). Předpokládá se, že tím vznikne obtěžující nejednoznačnost pro jednoduché, běžné případy [30] s malým nebo žádným přínosem. Někdy v budoucnu budeme možná hledat způsob, jak odhalit, pokudRETURNING -projektované n-tice byly vloženy a aktualizovány, ale to pravděpodobně není nutné, aby to bylo v první potvrzené iteraci funkce [31].

Nicméně , neměli byste aktualizovat řádky, které by neměly být aktualizovány. Prázdné aktualizace jsou téměř stejně drahé jako běžné aktualizace – a mohou mít nezamýšlené vedlejší účinky. Pro začátek nutně nepotřebujete UPSERT, váš případ vypadá spíše jako "SELECT or INSERT". Související:

  • Je SELECT nebo INSERT ve funkci náchylný k závodům?

Jedna čistší způsob vložení sady řádků by byl s CTE modifikujícími data:

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);

Přidaná složitost by měla platit za velké tabulky, kde INSERT je pravidlo a SELECT výjimka.

Původně jsem přidal NOT EXISTS predikát na posledním SELECT aby se předešlo duplicitám ve výsledku. Ale to bylo zbytečné. Všem CTE jednoho dotazu se zobrazují stejné snímky tabulek. Sada se vrátila s ON CONFLICT (name, url, email) DO NOTHING se vzájemně vylučuje pro sadu vrácenou po INNER JOIN ve stejných sloupcích.

Bohužel se tím také otevře malé okno pro podmínku závodu . Pokud ...

  • souběžná transakce vloží konfliktní řádky
  • zatím se nezavázal
  • ale nakonec se zaváže

... některé řádky mohou být ztraceny.

Můžete jen INSERT .. ON CONFLICT DO NOTHING , za kterým následuje samostatný SELECT dotaz pro všechny řádky - v rámci stejné transakce k překonání tohoto. Což zase otevírá další malé okno pro podmínku závodu pokud mohou souběžné transakce potvrdit zápisy do tabulky mezi INSERT a SELECT (ve výchozím nastavení READ COMMITTED úroveň izolace). Lze se mu vyhnout pomocí REPEATABLE READ izolace transakcí (nebo přísnější). Nebo s (možná drahým nebo dokonce nepřijatelným) zámkem zápisu na celý stůl. Můžete získat jakékoli chování, které potřebujete, ale může za to zaplatit.

Související:

  • Jak používat RETURNING s ON CONFLICT v PostgreSQL?
  • Vraťte řádky z INSERT pomocí ON CONFLICT bez nutnosti aktualizace



  1. Jak připojit Airflow k databázi Oracle

  2. Funkce IIf() vs. Příkaz IIf().

  3. Jak vytvořím a dotazuji propojené databázové servery v SQL Server?

  4. Pyodbc – název zdroje dat nebyl nalezen a není zadán žádný výchozí ovladač