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.