sql >> Base de Datos >  >> RDS >> PostgreSQL

Eliminar padre si ningún otro hijo hace referencia a él

En PostgreSQL 9.1 o posterior puede hacer esto con una sola declaración usando un CTE de modificación de datos . Esto es generalmente menos propenso a errores. minimiza el marco de tiempo entre los dos DELETE en los que una condiciones de carrera podría conducir a resultados sorprendentes con operaciones simultáneas:

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

Violín SQL.

El niño se elimina en cualquier caso. Cito el manual:

Declaraciones de modificación de datos en WITH se ejecutan exactamente una vez y siempre hasta el final , independientemente de si la consulta principal lee todos (o de hecho cualquiera) de su salida. Tenga en cuenta que esto es diferente de la regla para SELECT en WITH :como se indicó en la sección anterior, la ejecución de un SELECT se lleva solo hasta donde la consulta principal exige su salida.

El padre solo se elimina si no tiene otro niños.
Tenga en cuenta la última condición. Al contrario de lo que cabría esperar, esto es necesario, ya que:

Las sub-declaraciones en WITH se ejecutan concurrentemente entre sí y con la consulta principal. Por lo tanto, al usar sentencias de modificación de datos en WITH , el orden en que ocurren las actualizaciones especificadas es impredecible. Todas las sentencias se ejecutan con la misma instantánea (consulte el Capítulo 13), por lo que no pueden "ver" los efectos de las demás en las tablas de destino.

Énfasis en negrita mío.
Utilicé el nombre de columna parent_id en lugar del id no descriptivo .

Eliminar condición de carrera

Para eliminar posibles condiciones de carrera que mencioné anteriormente completamente , bloquee la fila principal primero . Por supuesto, todos operaciones similares deben seguir el mismo procedimiento para que funcione.

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

De esta manera solo uno transacción a la vez puede bloquear el mismo padre. Por lo tanto, no puede suceder que múltiples transacciones eliminen hijos del mismo padre, aún vean otros hijos y perdonen al padre, mientras que todos los hijos desaparecen después. (Las actualizaciones en columnas sin clave aún se permiten con FOR NO KEY UPDATE .)

Si tales casos nunca ocurren o puede vivir con eso (casi nunca), la primera consulta es más barata. De lo contrario, este es el camino seguro.

FOR NO KEY UPDATE se introdujo con Postgres 9.4. Detalles en el manual. En versiones anteriores, use el bloqueo más fuerte FOR UPDATE en su lugar.