SQL Server - 使用递归外键级联DELETE

时间:2014-07-15 15:49:42

标签: sql sql-server recursion constraints cascade

我花了很多时间试图弄清楚如何在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上有一个PK / FK,CASCADE ON DELETE。

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

DELETE FROM employee WHERE id=7

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

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

  • 汉密尔顿
  • 斯科特
  • 苏珊
  • 赛斯
  • 瑞克
  • 雷切

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

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,它的行为完全符合我的要求。同样,如果父母,祖父母,曾祖父母等被删除,我想要父母的每个可能的孩子,孙子,曾孙等都要删除。

当我尝试使用触发器时,我似乎无法触发它(例如,当你试图删除汉密尔顿的员工Susan时,先看看Susan是否有员工等)更不用说降低N-number员工。

原来如此!我想我已经提供了我能想到的每一个细节。如果某些事情仍然不明确,我会尝试改进这种描述。

3 个答案:

答案 0 :(得分:5)

Necromancing。
有两个简单的解决方案。

  • 你可以阅读微软的遗憾原因,告诉他们为什么不这样做 实现这个(因为它很困难且耗时 - 而且时间就是金钱),并解释为什么你不/不应该(尽管你这样做),并用存储中的光标实现删除功能程序
    • 因为你真的不需要删除级联,因为你总是有时间随时随地改变所有你和所有其他人的代码(比如与其他系统的接口)删除员工(或员工,注意:复数)(包括所有上级和下级对象[包括添加一个或几个新对象])在此数据库中(以及此数据库的任何其他副本供其他客户使用,尤其是在您无法访问数据库时的生产中[哦,在测试系统,集成系统,以及生产,测试和集成的本地副本]

  • 您可以使用实际支持递归级联删除的正确DBMS,例如PostGreSQL(只要图表是定向的,非循环的;否则在删除时出现ERROR)。

PS:这是讽刺。

答案 1 :(得分:3)

以下内容可能对您有用(我还没有对其进行测试,因此可能需要进行一些调整)。似乎所有你要做的就是删除层次结构底部的员工,然后再删除更高层次的员工。使用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

答案 2 :(得分:1)

这可能听起来很极端,但我不认为你想要做的事情有一个简单的烘焙选项。我建议创建一个可以执行以下操作的proc:

  1. 禁用FK约束
  2. 使用递归CTE获取要删除的员工列表(将其保存在临时表中)
  3. 从父/子表中删除行
  4. 从员工信息表中删除行
  5. 启用FK约束
  6. 将整个事物包裹在事务中以保持一致性