PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Excluir pai se não for referenciado por nenhum outro filho


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 em WITH 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 para SELECT em WITH :conforme indicado na seção anterior, execução de um SELECT é 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 em WITH são executados simultaneamente entre si e com a consulta principal. Portanto, ao usar instruções de modificação de dados em WITH , 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.