自引用外键约束并删除

时间:2022-12-16 00:15:03

what is the recommended way to handle self-referencing foreignkey constraints in SQL-Server?

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

Table-Model:

自引用外键约束并删除

fiData references a previous record in tabData. If i delete a record that is referenced by fiData, the database throws an exception:

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

"The DELETE statement conflicted with the SAME TABLE REFERENCE constraint "FK_tabDataPrev_tabDataNext". The conflict occurred in database "MyDataBase", table "dbo.tabData", column 'fiData'"

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

if Enforce Foreignkey Constraint is set to "Yes".

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

I don't need to cascade delete records that are referenced but i would need to set fiData=NULL where it's referenced. My idea is to set Enforce Foreignkey Constraint to "No" and create a delete-trigger. Is this recommendable or are there better ways?

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

Thank you.

3 个解决方案

#1


7  

Unlike Andomar, I'd be happy using a trigger - but I wouldn't remove the constraint checking. If you implement it as an instead of trigger, you can reset the other rows to null before performing the actual delete:

与Andomar不同,我很乐意使用触发器 - 但我不会删除约束检查。如果将其实现为而不是触发器,则可以在执行实际删除之前将其他行重置为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)

It's short, it's succinct, it wouldn't be necessary if SQL Server could handle foreign key cascades to the same table (in other RDBMS', you may be able to just specify ON DELETE SET NULL for the foreign key constraint, YMMV).

它简短,简洁,如果SQL Server可以处理同一个表的外键级联(在其他RDBMS中,您可能只能为外键约束YMMV指定ON DELETE SET NULL)则没有必要。

#2


2  

Triggers add implicit complexity. In a database with triggers, you won't know what a SQL statement does by looking at it. In my experience triggers are a bad idea with no exceptions.

触发器增加了隐式复杂性。在具有触发器的数据库中,您不会通过查看它来了解SQL语句的作用。根据我的经验,触发器是一个坏主意,没有例外。

In your example, setting the enforced constrained to "No" means you could add a nonexistent ID. And the query optimizer will be less effective because it can't assume the key is valid.

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

Consider creating a stored procedure instead:

请考虑创建存储过程:

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


0  

This very late to answer.

这个回答很晚。

But for some one who is searching like me.

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

and want to cascade

并希望级联

here is very good explanation

这里有很好的解释

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

The Problem Although you can define a foreign key with CASCADE DELETE in SQL Server, recursive cascading deletes are not supported (i.e. cascading delete on the same table).

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

If you create an INSTEAD OF DELETE trigger, this trigger only fires for the first DELETE statement, and does not fire for records recursively deleted from this trigger.

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

This behavior is documented on MSDN for SQL Server 2000 and SQL Server 2005.

SQL Server 2000和SQL Server 2005的MSDN上记录了此行为。

The Solution Suppose you have a table defined like this:

解决方案假设您有一个如下定义的表:

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

then the delete trigger looks like this:

然后删除触发器如下所示:

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

#1


7  

Unlike Andomar, I'd be happy using a trigger - but I wouldn't remove the constraint checking. If you implement it as an instead of trigger, you can reset the other rows to null before performing the actual delete:

与Andomar不同,我很乐意使用触发器 - 但我不会删除约束检查。如果将其实现为而不是触发器,则可以在执行实际删除之前将其他行重置为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)

It's short, it's succinct, it wouldn't be necessary if SQL Server could handle foreign key cascades to the same table (in other RDBMS', you may be able to just specify ON DELETE SET NULL for the foreign key constraint, YMMV).

它简短,简洁,如果SQL Server可以处理同一个表的外键级联(在其他RDBMS中,您可能只能为外键约束YMMV指定ON DELETE SET NULL)则没有必要。

#2


2  

Triggers add implicit complexity. In a database with triggers, you won't know what a SQL statement does by looking at it. In my experience triggers are a bad idea with no exceptions.

触发器增加了隐式复杂性。在具有触发器的数据库中,您不会通过查看它来了解SQL语句的作用。根据我的经验,触发器是一个坏主意,没有例外。

In your example, setting the enforced constrained to "No" means you could add a nonexistent ID. And the query optimizer will be less effective because it can't assume the key is valid.

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

Consider creating a stored procedure instead:

请考虑创建存储过程:

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


0  

This very late to answer.

这个回答很晚。

But for some one who is searching like me.

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

and want to cascade

并希望级联

here is very good explanation

这里有很好的解释

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

The Problem Although you can define a foreign key with CASCADE DELETE in SQL Server, recursive cascading deletes are not supported (i.e. cascading delete on the same table).

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

If you create an INSTEAD OF DELETE trigger, this trigger only fires for the first DELETE statement, and does not fire for records recursively deleted from this trigger.

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

This behavior is documented on MSDN for SQL Server 2000 and SQL Server 2005.

SQL Server 2000和SQL Server 2005的MSDN上记录了此行为。

The Solution Suppose you have a table defined like this:

解决方案假设您有一个如下定义的表:

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

then the delete trigger looks like this:

然后删除触发器如下所示:

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