sql >> Database >  >> RDS >> PostgreSQL

Verwijder ouder als er niet naar verwezen wordt door een ander kind

In PostgreSQL 9.1 of hoger u kunt dit doen met een enkele instructie met behulp van een data-modifying CTE . Dit is over het algemeen minder foutgevoelig. Het minimaliseert het tijdsbestek tussen de twee DELETE's waarin een race-omstandigheden kan tot verrassende resultaten leiden bij gelijktijdige bewerkingen:

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.

Het kind wordt in ieder geval verwijderd. Ik citeer de handleiding:

Verklaringen voor het wijzigen van gegevens in WITH worden precies één keer uitgevoerd, enaltijd volledig , onafhankelijk van het feit of de primaire query alle (of zelfs enige) van hun uitvoer leest. Merk op dat dit anders is dan de regel voor SELECT in WITH :zoals vermeld in de vorige sectie,uitvoering van een SELECT wordt alleen uitgevoerd voor zover de primaire query de uitvoer vereist.

De ouder wordt alleen verwijderd als deze geen andere . heeft kinderen.
Let op de laatste voorwaarde. In tegenstelling tot wat men zou verwachten, is dit nodig, omdat:

De sub-statements in WITH worden gelijktijdig uitgevoerd met elkaar en met de hoofdvraag. Daarom, bij gebruik van data-modifyingstatements in WITH , is de volgorde waarin de opgegeven updates daadwerkelijk plaatsvinden onvoorspelbaar. Alle instructies worden uitgevoerd met dezelfde momentopname (zie hoofdstuk 13), zodat ze elkaars effecten op de doeltabellen niet kunnen "zien".

Vetgedrukte nadruk van mij.
Ik gebruikte de kolomnaam parent_id in plaats van de niet-beschrijvende id .

Elimineren van raceconditie

Om mogelijke race-omstandigheden te elimineren die ik hierboven heb genoemd volledig , vergrendel de bovenliggende rij eerst . Natuurlijk, allemaal soortgelijke bewerkingen moeten dezelfde procedure volgen om het te laten werken.

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

Op deze manier slechts één transactie tegelijk kan dezelfde ouder vergrendelen. Het kan dus niet gebeuren dat meerdere transacties kinderen van dezelfde ouder verwijderen, nog andere kinderen zien en de ouder sparen, terwijl alle kinderen daarna weg zijn. (Updates van niet-sleutelkolommen zijn nog steeds toegestaan ​​met FOR NO KEY UPDATE .)

Als dergelijke gevallen zich nooit voordoen of u kunt ermee leven dat het (nauwelijks) gebeurt, is de eerste vraag goedkoper. Anders is dit het veilige pad.

FOR NO KEY UPDATE werd geïntroduceerd met Postgres 9.4. Details in de handleiding. Gebruik in oudere versies het sterkere slot FOR UPDATE in plaats daarvan.



  1. Download de taal die momenteel in SQL Server wordt gebruikt

  2. PostgreSQL-vensterfunctie:partitie in vergelijking

  3. FROM trefwoord niet gevonden waar verwacht (Oracle SQL)

  4. Hoe kan ik (hoofdletterongevoelig) in een kolom zoeken met LIKE-wildcard?