No PostgreSQL 9.1 ou posterior você pode fazer isso com uma única instrução usando um CTE de modificação de dados . Isso geralmente é menos propenso a erros. Ele minimiza o intervalo de tempo entre os dois DELETEs em que uma condições de corrida pode levar a resultados surpreendentes com operações 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 -- !
);
SQL Fiddle.
A criança é excluída em qualquer caso. Cito o manual:
Instruções de modificação de dados emWITH
são executados exatamente uma vez esempre até a conclusão , independentemente de a consulta primária ler tudo (ou qualquer um) de sua saída. Observe que isso é diferente da regra paraSELECT
emWITH
:conforme indicado na seção anterior, execução de umSELECT
é transportado apenas até onde a consulta primária exige sua saída.
O pai só é excluído se não tiver outro filhos.
Observe a última condição. Ao contrário do que se poderia esperar, isso é necessário, pois:
As subinstruções emWITH
são executados simultaneamente entre si e com a consulta principal. Portanto, ao usar instruções de modificação de dados emWITH
, a ordem em que as atualizações especificadas realmente acontecem é imprevisível. Todas as instruções são executadas com o mesmo instantâneo (veja o Capítulo 13), então elas não podem "ver" os efeitos umas das outras nas tabelas de destino.
A ênfase em negrito é minha.
Usei o nome da coluna
parent_id
no lugar do id
não descritivo . Eliminar condição de corrida
Para eliminar possíveis condições de corrida que mencionei acima completamente , bloqueie a linha pai primeiro . Claro, todos operações semelhantes devem seguir o mesmo procedimento para fazê-lo funcionar.
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 -- !
);
Dessa forma, apenas um transação de cada vez pode bloquear o mesmo pai. Portanto, não pode acontecer que várias transações excluam filhos do mesmo pai, ainda vejam outros filhos e poupem o pai, enquanto todos os filhos desaparecem depois. (Atualizações em colunas não chave ainda são permitidas com
FOR NO KEY UPDATE
.) Se tais casos nunca ocorrerem ou você puder conviver com isso (quase nunca) acontecendo - a primeira consulta é mais barata. Caso contrário, este é o caminho seguro.
FOR NO KEY UPDATE
foi introduzido com o Postgres 9.4. Detalhes no manual. Em versões mais antigas, use o bloqueio mais forte FOR UPDATE
em vez de.