7

在SQL-Server中处理自引用外键约束的推荐方式是什么?自引用外键约束和删除

表 - 型号:

enter image description here

fiData引用在tabData以前的纪录。如果我删除由fiData引用的记录,该数据库将引发异常:

“DELETE语句冲突以引用同一表 约束‘FK_tabDataPrev_tabDataNext’。该冲突发生于 数据库‘MyDatabase的’,表“dbo.tabData”,列'fiData'“

如果Enforce Foreignkey Constraint设置为”是“。

我不需要级联删除被引用的记录,但我需要在其引用的位置设置fiData=NULL。我的想法是将Enforce Foreignkey Constraint设置为“否”并创建删除触发器。这是值得推荐的还是有更好的方法?

谢谢。

回答

7

不像Andomar,我很乐意使用触发器 - 但我不会删除约束检查。如果要实现它作为一个instead of触发,你可以在执行之前,其他行空重实际删除:

CREATE TRIGGER T_tabData_D 
on tabData 
instead of delete 
as 
    set nocount on 
    update tabData set fiData = null where fiData in (select idData from deleted) 
    delete from tabData where idData in (select idData from deleted) 

它的短,它的简洁,它不会是必要的,如果SQL服务器可以处理外键级联到同一个表(在其他RDBMS中,您可能只需指定ON DELETE SET NULL作为外键约束YMMV)。

+1

谢谢。但是我在创建触发器时遇到了一个异常:“INSTEAD OF DELETE/UPDATE触发器无法在具有DELETE/UPDATE动作定义的级联的外键的表上定义”。 – 2011-03-24 12:31:04

+0

我知道这个评论太迟了,但对于像我这样搜索的人来说。 这只会删除一个条目。它不会递归地级联。 – Arif 2012-10-05 11:56:39

+0

@Arif - 这是“ON DELETE SET NULL”,而不是“ON DELETE CASCADE”。前者从不需要递归。对于“ON DELETE CASCADE”,我推荐一个CTE,它首先计算所有“ID”值的闭包,然后执行删除操作。 – 2012-10-05 11:59:59

2

触发器添加隐含的复杂性。在一个带有触发器的数据库中,通过查看它,你不会知道SQL语句的作用。根据我的经验,触发器是个不错的主意,没有例外。

在您的示例中,将强制约束设置为“否”意味着您可以添加不存在的ID。而且查询优化器效率不高,因为它不能假定密钥是有效的。

考虑创建一个存储过程,而不是:

create procedure dbo.NukeTabData(
    @idData int) 
as 
begin transaction 
update tabData set fiData = null where fiData = @idData 
delete from tabData where idData = @idData 
commit transaction 
go 
+3

现在你的调用代码有'exec NukeTabData(ID)',你不知道没有检查过程内容会发生什么。为什么没有,当一个触发器不是? – 2011-03-24 10:49:06

+2

@Damien_The_Unbeliever:存储过程是明确的:如果我对细节感兴趣,它告诉我应该在哪里看。一个触发器是隐含的:“从tabData删除idData = 42'暗示存在将运行的附加更新。例如,在运行delete之后,触发器可能会使'@@ rowcount'为4而不是1.这导致了一些棘手的错误 - 特别是对于新的代码库开发人员。 – Andomar 2011-03-24 10:54:00

+0

@@ ROWCOUNT是基于范围的 - 它不受触发器内的任何活动影响。你可能会想到@@ IDENTITY,但大多数人知道现在要避免这种情况 – 2011-03-24 11:18:22

0

这很晚才回答。

但是对于像我这样寻找的人来说。

,并希望cascade

这里很好解释

http://devio.wordpress.com/2008/05/23/recursive-delete-in-sql-server/

的问题 虽然你可以定义CASCADE外键在SQL服务器中删除,递归级联删除,不支持(即在同一个表上级联删除)。

如果您创建INSTEAD OF DELETE触发器,则此触发器仅触发第一个DELETE语句,并且不会触发从此触发器递归删除的记录。

此行为是在MSDN上记录的SQL Server 2000和SQL Server 2005

解决方案 假设你有一个像这样定义的表:

CREATE TABLE MyTable (
    OID INT,  -- primary key 
    OID_Parent INT, -- recursion 
    ... other columns 
) 

然后删除触发器看起来是这样的:

CREATE TRIGGER del_MyTable ON MyTable INSTEAD OF DELETE 
AS 
    CREATE TABLE #Table(
     OID INT 
    ) 
INSERT INTO #Table (OID) 
SELECT OID 
FROM deleted 

DECLARE @c INT 
SET @c = 0 

WHILE @c <> (SELECT COUNT(OID) FROM #Table) BEGIN 
    SELECT @c = COUNT(OID) FROM #Table 

    INSERT INTO #Table (OID) 
    SELECT MyTable.OID 
    FROM MyTable 
    LEFT OUTER JOIN #Table ON MyTable.OID = #Table.OID 
    WHERE MyTable.OID_Parent IN (SELECT OID FROM #Table) 
    AND  #Table.OID IS NULL 
END 

DELETE MyTable 
FROM MyTable 
INNER JOIN #Table ON MyTable.OID = #Table.OID 

GO 
+0

这是一个很好的答案ON DELETE CASCADE,但问题是关于ON DELETE SET NULL – dajoropo 2016-11-15 09:22:59