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

Smazat rodiče, pokud na něj neodkazuje žádný jiný potomek

V PostgreSQL 9.1 nebo novější můžete to udělat jediným příkazem pomocí CTE modifikujícího data . To je obecně méně náchylné k chybám. Minimalizuje časový rámec mezi dvěma DELETE, ve kterém podmínky závodu může vést k překvapivým výsledkům při souběžných operacích:

WITH del_child AS (
    DELETE FROM child
    WHERE  child_id = 1
    RETURNING parent_id, child_id
    )
DELETE FROM parent p
USING  del_child x
WHERE  p.parent_id = x.parent_id
AND    NOT EXISTS (
   SELECT 1
   FROM   child c
   WHERE  c.parent_id = x.parent_id
   AND    c.child_id <> x.child_id   -- !
   );

SQL Fiddle.

Dítě je v každém případě smazáno. Cituji manuál:

Příkazy upravující data v WITH jsou provedeny přesně jednou avždy do dokončení nezávisle na tom, zda primární dotaz přečte všechny (nebo skutečně jakýkoli) jejich výstup. Všimněte si, že se to liší od pravidla pro SELECT v WITH :jak je uvedeno v předchozí části, provedení SELECT je přenášen pouze tak daleko, jak primární dotaz vyžaduje jeho výstup.

Rodič je smazán pouze v případě, že nemá žádné jiné děti.
Všimněte si poslední podmínky. Na rozdíl od toho, co by se dalo očekávat, je to nutné, protože:

Dílčí příkazy v WITH jsou prováděny současně mezi sebou as hlavním dotazem. Proto při použití příkazů modifikujících data v WITH , pořadí, ve kterém se zadané aktualizace skutečně stanou, je nepředvídatelné. Všechny příkazy jsou prováděny se stejným snímkem (viz kapitola 13), takže nemohou „vidět“ vzájemné účinky na cílové tabulky.

Tučné zdůraznění min.
Použil jsem název sloupce parent_id místo nepopisného id .

Eliminace race condition

Abych eliminoval možné podmínky závodu, zcela jsem zmínil výše , nejprve uzamkněte nadřazený řádek . Samozřejmě, vše aby podobné operace fungovaly, musí se řídit stejným postupem.

WITH lock_parent AS (
   SELECT p.parent_id, c.child_id
   FROM   child  c
   JOIN   parent p ON p.parent_id = c.parent_id
   WHERE  c.child_id = 12              -- provide child_id here once
   FOR    NO KEY UPDATE                -- locks parent row.
   )
 , del_child AS (
   DELETE FROM child c
   USING  lock_parent l
   WHERE  c.child_id = l.child_id
   )
DELETE FROM parent p
USING  lock_parent l
WHERE  p.parent_id = l.parent_id
AND    NOT EXISTS (
   SELECT 1
   FROM   child c
   WHERE  c.parent_id = l.parent_id
   AND    c.child_id <> l.child_id   -- !
   );

Tímto způsobem pouze jeden transakce najednou může uzamknout stejného rodiče. Nemůže se tedy stát, že více transakcí odstraní děti stejného rodiče, stále uvidí další děti a ušetří rodiče, zatímco všechny děti budou poté pryč. (Aktualizace neklíčových sloupců jsou stále povoleny pomocí FOR NO KEY UPDATE .)

Pokud se takové případy nikdy nevyskytnou nebo se s tím (málokdy) dá žít - první dotaz je levnější. Jinak toto je bezpečná cesta.

FOR NO KEY UPDATE byl představen s Postgres 9.4. Podrobnosti v návodu. Ve starších verzích použijte silnější zámek FOR UPDATE místo toho.



  1. SQL řádky do sloupců

  2. Oracle najít omezení

  3. C# Ekvivalent datových typů SQL Server

  4. Optimalizace SELECT dotazu, který běží pomalu na Oracle, který běží rychle na SQL Server