SQL Server - 使用递归外键级联删除

Posted

技术标签:

【中文标题】SQL Server - 使用递归外键级联删除【英文标题】:SQL Server - Cascading DELETE with Recursive Foreign Keys 【发布时间】:2014-09-05 21:53:45 【问题描述】:

一段时间以来,我花了很多时间试图弄清楚如何为 SQL Server 上的递归主键实现 CASCADE ON DELETE。我已经阅读了有关触发器、创建临时表等的内容,但尚未找到适合我的数据库设计的答案。

这是一个用于演示目的的 Boss/Employee 数据库示例:

TABLE employee
id|name     |boss_id
--|---------|-------
1 |John     |1
2 |Hillary  |1
3 |Hamilton |1
4 |Scott    |2
5 |Susan    |2
6 |Seth     |2
7 |Rick     |5
8 |Rachael  |5

如您所见,每个员工都有一个老板,老板也是员工。所以,在id/boss_id上有PK/FK的关系。

这是一个包含他们信息的(缩写)表:

TABLE information
emp_id|street     |phone
------|-----------|-----
2     |blah blah  |blah
6     |blah blah  |blah
7     |blah blah  |blah

employee.id/information.emp_id 上有一个带有 CASCADE ON DELETE 的 PK/FK。

例如,如果 Rick 被解雇,我们会这样做:

DELETE FROM employee WHERE id=7

这应该从员工和信息中删除 Rick 的行。耶级联!

现在,假设我们经历了艰难时期,我们需要让汉密尔顿和他的整个部门休息。这意味着我们需要删除

汉密尔顿 斯科特 苏珊 赛斯 瑞克 瑞秋

我们运行时从员工表和信息表中:

DELETE FROM employee WHERE id=3

我为 id/emp_id 尝试了一个简单的 CASCADE ON DELETE,但 SQL Server 没有:

Introducing FOREIGN KEY constraint 'fk_boss_employee' on table 'employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

我能够在 Access 中的测试数据库上使用 CASCADE ON DELETE,它的行为完全符合我的要求。同样,如果父母、祖父母、曾祖父母等被删除,我希望删除父母的每个可能的孩子、孙子女、曾孙子女等。

当我尝试使用触发器时,我似乎无法让它自行触发(例如,当您尝试删除 Hamilton 的员工 Susan 时,首先查看 Susan 是否有任何员工等),更不用说下降 N 个员工。

所以!我想我已经提供了我能想到的每一个细节。如果还有什么不清楚的地方,我会尝试改进这个描述。

【问题讨论】:

【参考方案1】:

这听起来可能很极端,但我认为没有一个简单的选项可以满足您的需求。我建议创建一个可以执行以下操作的 proc:

    禁用 FK 约束 使用递归 CTE 获取要删除的员工列表(将其保存在临时表中) 从父/子表中删除行 从员工信息表中删除行 启用 FK 约束

将整个事物包装在事务中以保持一致性

【讨论】:

你不需要禁用 FK,如果你使用 Delete from employee WHERE id in ( /* SOME CTE EXPR */ ) Sql server 将检查所有引用是否都在删除的对象中有了它【参考方案2】:

以下内容可能对您有用(我尚未对其进行测试,因此可能需要进行一些调整)。似乎您所要做的就是在删除更高级别的员工之前从层次结构的底部删除员工。使用 CTE 递归地构建删除层次结构,并按员工的层次结构级别降序排列 CTE 输出。然后按顺序删除。

CREATE PROC usp_DeleteEmployeeAndSubordinates (@empId INT)
AS

;WITH employeesToDelete AS (
    SELECT  id, CAST(1 AS INT) AS empLevel
    FROM    employee
    WHERE   id = @empId
    UNION ALL
    SELECT  e.id, etd.empLevel + 1
    FROM    employee e
            JOIN employeesToDelete etd ON e.boss_id = etd.id AND e.boss_id != e.id
)
SELECT  id, ROW_NUMBER() OVER (ORDER BY empLevel DESC) Ord
INTO    #employeesToDelete
FROM    employeesToDelete;

DECLARE @current INT = 1, @max INT = @@ROWCOUNT;

WHILE @current <= @max
BEGIN
    DELETE employee WHERE id = (SELECT id FROM #employeesToDelete WHERE Ord = @current);
    SET @current = @current + 1;
END;
GO

【讨论】:

修复了一些语法问题后,我可以确认这个过程完全符合我的要求。非常感谢!【参考方案3】:

死灵术。 有2个简单的解决方案。

您可以阅读 Microsoft 的抱歉理由,说明他们为什么不这样做 实现这一点(因为它既困难又耗时 - 时间就是金钱),并解释为什么你不需要/不应该需要它(尽管你这样做),并使用存储的游标实现删除功能程序 因为您实际上并不需要删除级联,因为您总是有时间随时随地更改您和所有其他人的代码(如与其他系统的接口),以删除员工(或员工,注意:复数)(包括所有上级和下级对象[包括添加一个或多个新对象时])(以及其他客户的此数据库的任何其他副本,尤其是在生产中当您无权访问数据库时[哦,关于测试系统、集成系统以及生产、测试​​和集成的本地副本]

您可以使用适当的 DBMS,它实际上支持递归级联删除,例如 PostGreSQL(只要图形是有向的且非循环的;否则删除时出现错误)。

PS:这是讽刺。


注意:

只要您的删除不是源于级联,并且您只是想对自引用表执行删除,您就可以删除任何条目,只要您删除所有从属对象以及 in-条款。

所以要删除这样的对象,请执行以下操作:

;WITH CTE AS 
(
    SELECT id, boss_id, [name] FROM employee
    -- WHERE boss_id IS NULL 
    WHERE id = 2 -- <== this here is the id you want to delete !

    UNION ALL

    SELECT employee.id, employee.boss_id, employee.[name] FROM employee
    INNER JOIN CTE ON CTE.id = employee.boss_id 
)
DELETE FROM employee 
WHERE employee.id IN (SELECT id FROM CTE)

假设你有如下表结构:

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.employee') AND type in (N'U'))
BEGIN
CREATE TABLE dbo.employee
(
    id int NOT NULL,
    boss_id int NULL,
    [name] varchar(50) NULL,
    CONSTRAINT PK_employee PRIMARY KEY ( id )
); 
END
GO

IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'dbo.FK_employee_employee') AND boss_id_object_id = OBJECT_ID(N'dbo.employee'))
ALTER TABLE dbo.employee  WITH CHECK ADD  CONSTRAINT FK_employee_employee FOREIGN KEY(boss_id)
REFERENCES dbo.employee (id)
GO

IF  EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'dbo.FK_employee_employee') AND boss_id_object_id = OBJECT_ID(N'dbo.employee'))
ALTER TABLE dbo.employee CHECK CONSTRAINT FK_employee_employee
GO

【讨论】:

撇开讽刺不谈,微软真的为此做了抱歉的借口吗?有参考吗?

以上是关于SQL Server - 使用递归外键级联删除的主要内容,如果未能解决你的问题,请参考以下文章

python django中的orm外键级联删除

mysql外键级联更新删除

依赖外键级联不好吗?

MySQL约束 主键约束丨唯一约束丨非空约束丨外键级联

SQL Server:删除表级联等效?

sql server 级联更新 急急急!