自引用外键约束并删除

时间:2011-03-24 09:13:23

标签: sql sql-server-2005 tsql constraints foreign-key-relationship

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

表 - 型号:

enter image description here

fiData引用tabData中的上一条记录。如果我删除了fiData引用的记录,则数据库会抛出异常:

  

“DELETE语句与SAME TABLE REFERENCE冲突   约束“FK_tabDataPrev_tabDataNext”。冲突发生在   数据库“MyDataBase”,表“dbo.tabData”,列'fiData'“

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

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

谢谢。

3 个答案:

答案 0 :(得分:7)

与Andomar不同,我很乐意使用触发器 - 但我不会删除约束检查。如果将其实现为instead of触发器,则可以在执行实际删除之前将其他行重置为null:

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 Server可以处理同一个表的外键级联(在其他RDBMS中,您可能只能为外键约束指定ON DELETE SET NULL则没有必要,YMMV)。

答案 1 :(得分: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

答案 2 :(得分:0)

这回答很晚。

但对于像我这样搜索的人来说。

并希望cascade

这里有很好的解释

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

问题 尽管可以在SQL Server中使用CASCADE DELETE定义外键,但不支持递归级联删除(即在同一个表上进行级联删除)。

如果创建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