sql >> Databasteknik >  >> RDS >> PostgreSQL

Ta bort förälder om den inte hänvisas till av något annat barn

I PostgreSQL 9.1 eller senare du kan göra detta med ett enda uttalande med en datamodifierande CTE . Detta är i allmänhet mindre felbenäget. Det minimerar tidsramen mellan de två DELETEs där en tävlingsförhållanden kan leda till överraskande resultat med samtidiga operationer:

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-fiol.

Barnet raderas i alla fall. Jag citerar manualen:

Datamodifierande uttalanden i WITH exekveras exakt en gång ochalltid till slut , oberoende av om den primära frågan läser alla (eller faktiskt någon) av deras utdata. Observera att detta skiljer sig från regeln för SELECT i WITH :som anges i föregående avsnitt, exekvering av en SELECT utförs endast så långt som den primära frågan kräver dess utdata.

Föräldern tas bara bort om den inte har någon annan barn.
Observera det sista villkoret. Tvärtemot vad man kan förvänta sig är detta nödvändigt, eftersom:

Undersatserna i WITH exekveras samtidigt med varandra och med huvudfrågan. Därför, när du använder datamodifierande uttalanden i WITH , den ordning i vilken de angivna uppdateringarna faktiskt sker är oförutsägbar. Alla uttalanden exekveras med samma ögonblicksbild (se kapitel 13), så de kan inte "se" varandras effekter på måltabellerna.

Fet betoning min.
Jag använde kolumnnamnet parent_id i stället för det icke-beskrivande id .

Eliminera tävlingsvillkoret

För att eliminera möjliga tävlingsförhållanden nämnde jag ovan helt , lås den överordnade raden först . Naturligtvis alla liknande operationer måste följa samma procedur för att få det att fungera.

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

På så sätt bara en transaktion åt gången kan låsa samma förälder. Så det kan inte hända att flera transaktioner tar bort barn till samma förälder, fortfarande ser andra barn och skonar föräldern, medan alla barn är borta efteråt. (Uppdateringar av icke-nyckelkolumner är fortfarande tillåtna med FOR NO KEY UPDATE .)

Om sådana fall aldrig inträffar eller om du kan leva med att det (sällan) händer – är den första frågan billigare. Annars är det här den säkra vägen.

FOR NO KEY UPDATE introducerades med Postgres 9.4. Detaljer i manualen. I äldre versioner använd det starkare låset FOR UPDATE istället.



  1. MySQL INT betydelse

  2. Vilken är inställningen för att se tidsdelen med datum i Oracle PL/SQL-utvecklare?

  3. värde för långt för typteckenvarierande (N)

  4. Utveckling av feltolerans i PostgreSQL:Tidsresor