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_ITEMSprimeiro e, em seguida , as linhas de STOCK_ARTICLES.
- Use
ON DELETE CASCADEpara 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 EXISTScláusula de aninhamento que WHERE INpode 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_ITEMSpara 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 UPDATEor 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 MERGEinstruçã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 CASCADEpode ser perigoso , portanto, use com cuidado.
Nota lateral: use MERGE(ou UPDATE- e - INSERTonde MERGEnão estiver disponível) quando precisar de uma UPSERToperação, não DELETE substitua por - INSERTpara evitar cair nas armadilhas colocadas por outras pessoas ON DELETE CASCADE.
INVENTORY_ITEMSsendo adicionados entre os doisDELETEs.