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

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

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

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

  1. 创建新员工Alice和Dave,没有经理,因为他们是CEO和总裁。 因此对于这两个记录, tblEmployee.Manager为NULL。
  2. 以Alice为经理创建新员工Bob。 然后用鲍勃作为他的经理创建查尔斯。 它们的Manager字段包含tblEmployee中另一条记录的主键值。
  3. 编辑Alice的员工记录,意思是分配Dave有她的经理(这很好),但不小心将Alice的经理设为Charles,他是树中Alice的两级。

现在该表是循环引用而不是正确的树。

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

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

您可以使用CHECK CONSTRAINT执行此操作,该CHECK CONSTRAINTvalidation经理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 ) 

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


编辑:一个重要的注意事项:检查约束是否在它们引用的列上得到validation。 我最初将其编码为检查员工ID的周期,而不是经理ID。 但是,这不起作用,因为它仅在更改ID列时触发。 此版本确实有效,因为它会在ManagerID更改时触发。

您可以添加“级别”整数列。

Alice和Dave将具有级别== 0如果您将为员工设置经理他的(员工)级别将是他的经理级别+ 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小提琴

我认为最好的方法是:

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

    • 检查您的C#应用​​程序
    • 检查表Employee TRIGGER (最好的安全原因,你不知道每个开发人员是否会在更新员工之前检查,如果有人直接sql更新)

如果您分配给员工Y经理X不是Y员工Nx

无论如何,递归函数在SQL查询和C#App中都很有用

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