Esse é o ponto principal das restrições de chave estrangeira: elas impedem a exclusão de dados que são referidos em outros lugares, a fim de manter a integridade referencial.
Existem duas opções:
- Exclua as linhas do
INVENTORY_ITEMS
primeiro e, em seguida , as linhas de STOCK_ARTICLES
.
- Use
ON DELETE CASCADE
para na definição de chave.
1: Exclusão na ordem correta
A maneira mais eficiente de fazer isso varia dependendo da complexidade da consulta que decide quais linhas excluir. Um padrão geral pode ser:
BEGIN TRANSACTION
SET XACT_ABORT ON
DELETE INVENTORY_ITEMS WHERE STOCK_ARTICLE IN (<select statement that returns stock_article.id for the rows you are about to delete>)
DELETE STOCK_ARTICLES WHERE <the rest of your current delete statement>
COMMIT TRANSACTION
Isso é bom para consultas simples ou para excluir um único item de estoque, mas, como a instrução delete contém uma WHERE NOT EXISTS
cláusula de aninhamento que WHERE IN
pode produzir um plano muito ineficiente, teste com um tamanho realista do conjunto de dados e reorganize a consulta, se necessário.
Observe também as instruções da transação: você deseja garantir que as exclusões sejam concluídas ou nenhuma delas. Se a operação já estiver ocorrendo dentro de uma transação, você obviamente precisará alterar isso para corresponder ao seu processo atual de transação e tratamento de erros.
2: Use ON DELETE CASCADE
Se você adicionar a opção em cascata à sua chave estrangeira, o SQL Server fará isso automaticamente, removendo as linhas de INVENTORY_ITEMS
para satisfazer a restrição de que nada deve se referir às linhas que você está excluindo. Basta adicionar ON DELETE CASCADE
à definição FK da seguinte forma:
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Uma vantagem aqui é que a exclusão é uma instrução atômica que reduz (embora, como sempre, não remova 100%) a necessidade de se preocupar com as configurações de transação e bloqueio. A cascata pode até operar em vários níveis de pai / filho / neto / ... se houver apenas um caminho entre pai e todos os descendentes (procure "vários caminhos em cascata" para exemplos de onde isso pode não funcionar).
NOTA: Eu e muitos outros consideramos as exclusões em cascata perigosas; portanto, se você usar esta opção, tenha muito cuidado para documentá-la adequadamente no design do banco de dados, para que você e outros desenvolvedores não troquem o perigo posteriormente . Evito exclusões em cascata sempre que possível por esse motivo.
Um problema comum causado com exclusões em cascata é quando alguém atualiza dados descartando e recriando linhas em vez de usar UPDATE
or MERGE
. Isso geralmente é visto onde "atualizar as linhas que já existem, inserir aquelas que não existem" (às vezes chamada de operação UPSERT) é necessário e as pessoas que desconhecem a MERGE
instrução acham mais fácil:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
do que
-- updates
UPDATE target
SET <col1> = source.<col1>
, <col2> = source.<col2>
...
, <colN> = source.<colN>
FROM <target_table> AS target JOIN <source_table_or_view_or_statement> AS source ON source.ID = target.ID
-- inserts
INSERT <target_table>
SELECT *
FROM <source_table_or_other> AS source
LEFT OUTER JOIN
<target_table> AS target
ON target.ID = source.ID
WHERE target.ID IS NULL
O problema aqui é que a instrução de exclusão cascateará em linhas filhas e a instrução de inserção não as recriará. Portanto, ao atualizar a tabela pai, você acidentalmente perde dados das tabelas filho.
Sumário
Sim, você deve excluir as linhas filho primeiro.
Não há outra opção: ON DELETE CASCADE
.
Mas ON DELETE CASCADE
pode ser perigoso , portanto, use com cuidado.
Nota lateral: use MERGE
(ou UPDATE
- e - INSERT
onde MERGE
não estiver disponível) quando precisar de uma UPSERT
operação, não DELETE
substitua por - INSERT
para evitar cair nas armadilhas colocadas por outras pessoas ON DELETE CASCADE
.
INVENTORY_ITEMS
sendo adicionados entre os doisDELETE
s.