如何防止自引用表变为循环

时间:2014-04-11 17:52:06

标签: c# sql sql-server circular-reference

这是一个非常常见的问题,但我还没有找到我正在寻找的确切问题和答案。

我有一个表格,其FK指向自己的PK,以启用任意深层次结构,例如具有列Manager的经典tblEmployee,它是具有PK tblEmployee.EmployeeID的FK。

让我们在我的应用中说,用户

  1. 创建新员工Alice和Dave,没有经理,因为他们是CEO和总裁。因此,对于这两条记录,tblEmployee.Manager为NULL。
  2. 以Alice为经理创建新员工Bob。然后用Bob作为他的经理创建Charles。其Manager字段包含tblEmployee中另一条记录的主键值。
  3. 编辑Alice的员工记录,意思是分配Dave有她的经理(这很好),但不小心将Alice的经理设置为Charles,他是树中Alice的两级。
  4. 现在该表是循环引用而不是正确的树。

    确保应用程序中的第3步无法完成最佳方法是什么?我只需要确保它将拒绝执行最后一次SQL更新,而是显示一些错误消息。

    我不是在挑剔它是SQL Server中的数据库约束(必须在2008年或2012年工作),还是在我的C#app的业务逻辑层中使用某种验证例程。

4 个答案:

答案 0 :(得分:16)

您可以使用CHECK CONSTRAINT执行此操作,验证经理ID不是循环。您不能在检查约束中使用复杂查询,但如果首先将其包装在函数中,您可以:

create function CheckManagerCycle( @managerID int )
returns int
as
begin

    declare @cycleExists bit
    set @cycleExists = 0

    ;with cte as (
        select E.* from tblEmployee E where ID = @managerID
        union all
        select E.* from tblEmployee E join cte on cte.ManagerID = E.ID and E.ID <> @managerID
    )
    select @cycleExists = count(*) from cte E where E.ManagerID = @managerID

    return @cycleExists;

end

然后你可以使用这样的约束:

alter table tblEmployee
ADD CONSTRAINT chkManagerRecursive CHECK ( dbo.CheckManagerCycle(ManagerID) = 0 )

这将阻止添加或更新记录以从任何来源创建循环。


编辑:重要说明:检查约束是否在其引用的列上得到验证。我最初将其编码为检查员工ID的周期,而不是经理ID。但是,这不起作用,因为它仅在更改ID列时触发。此版本确实有效,因为只要ManagerID更改,它就会被触发。

答案 1 :(得分:2)

您可以添加'level'整数列。

Alice和Dave的等级为== 0 如果您将为员工设置经理,他(员工)级别将是他的经理级别+ 1。

更新期间您应该检查经理级别是否小于员工级别......

这比使用程序更快...

答案 2 :(得分:1)

您可以在UPDATE声明中加入支票:

DECLARE @Employee INT = 2
       ,@NewManager INT = 4
;WITH cte AS (SELECT *
              FROM tblEmployee
              WHERE Manager = @Employee
              UNION  ALL
              SELECT a.*
              FROM tblEmployee a
              JOIN cte b
                ON a.manager = b.EmployeeID)
UPDATE a
SET a.Manager = @NewManager
FROM tblEmployee a
WHERE EmployeeID = @Employee
    AND NOT EXISTS (SELECT *
                    FROM cte b
                    WHERE a.EmployeeID = b.Manager)

演示:SQL Fiddle

答案 3 :(得分:0)

我认为这是最好的方法:

  1. 在t-sql中创建2个递归函数(比脏循环执行更好),它将完成返回“给定员工的 N + x个管理员“和”给定经理的Nx员工
  2. 预防第3步,使用GET_MANAGERS_OFGET_EMPLOYEES_OF功能将同时用于:

    • 签入您的C#App
    • 检查表员工 TRIGGER (最好的安全原因是您不知道每个开发人员是否会在更新员工之前检查以及是否有人直接进行sql更新)
  3. 如果您分配给员工Y 经理X ,则员工N-x不是

    无论如何,递归函数在SQL查询和C#App

    中都很有用

    仅供参考,有一种方法可以在C#App中处理SQL ERROR TRANSACTION(“你可以这样做,因为......”)。