这是一个非常常见的问题,但我还没有找到我正在寻找的确切问题和答案。
我有一个表格,其FK指向自己的PK,以启用任意深层次结构,例如具有列Manager
的经典tblEmployee,它是具有PK tblEmployee.EmployeeID的FK。
让我们在我的应用中说,用户
tblEmployee.Manager
为NULL。tblEmployee
中另一条记录的主键值。现在该表是循环引用而不是正确的树。
确保应用程序中的第3步无法完成的最佳方法是什么?我只需要确保它将拒绝执行最后一次SQL更新,而是显示一些错误消息。
我不是在挑剔它是SQL Server中的数据库约束(必须在2008年或2012年工作),还是在我的C#app的业务逻辑层中使用某种验证例程。
答案 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)
我认为这是最好的方法:
预防第3步,使用GET_MANAGERS_OF
和GET_EMPLOYEES_OF
功能将同时用于:
如果您分配给员工Y 的经理X ,则员工N-x不是 。
无论如何,递归函数在SQL查询和C#App
中都很有用仅供参考,有一种方法可以在C#App中处理SQL ERROR TRANSACTION(“你可以这样做,因为......”)。