在SQL Server中,我有这个简化的表格,我试图通过他们的域管理器获取所有员工的列表:
IF OBJECT_ID('tempdb.dbo.#employees') IS NOT NULL DROP TABLE #employees
CREATE TABLE #employees (
empid int,
empname varchar(50),
mgrid int,
func varchar(50)
)
INSERT INTO #employees VALUES(1, 'Jeff', 2, 'Designer')
INSERT INTO #employees VALUES(2, 'Luke', 4, 'Head of designers')
INSERT INTO #employees VALUES(3, 'Vera', 2, 'Designer')
INSERT INTO #employees VALUES(4, 'Peter', 5, 'Domain Manager')
INSERT INTO #employees VALUES(5, 'Olivia', NULL, 'CEO')
;
WITH Emp_CTE AS (
SELECT empid, empname, func, mgrid AS dommgr
FROM #employees
UNION ALL
SELECT e.empid, e.empname, e.func, e.mgrid AS dommgr
FROM #employees e
INNER JOIN Emp_CTE ecte ON ecte.empid = e.mgrid
WHERE ecte.func <> 'Domain Manager'
)
SELECT * FROM Emp_CTE
所以我想要的输出是:
empid empname func dommgr
1 Jeff Designer 4
2 Luke Head of designers 4
3 Vera Designer 4
相反,我得到了这个错误:
Msg 530, Level 16, State 1, Line 17
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
我做错了什么?实际上是否可以使用CTE
?
编辑:数据确实存在错误,错误已经消失,但结果不是我想要的:
empid empname func dommgr
1 Jeff Designer 2
2 Luke Head of designers 4
3 Vera Designer 2
4 Peter Domain Manager 5
5 Olivia CEO NULL
4 Peter Domain Manager 5
1 Jeff Designer 2
3 Vera Designer 2
答案 0 :(得分:1)
你有两名员工在经理人中互相提交,所以一位是另一位经理。这导致了无限递归。递归树中也存在间隙,因为域管理器未在任何地方引用。您已经通过将Luke的mgrid更改为4来修复了样本数据。现在没有差距,也没有任何问题。 但是你也没有递归的根条目,第一个查询没有过滤器。
您可以使用此查询:
WITH DomainManager AS (
SELECT empid, empname, func, dommgr = empid, Hyrarchy = 1
FROM #employees
WHERE func = 'Domain Manager'
UNION ALL
SELECT e.empid, e.empname, e.func, dommgr, Hyrarchy = Hyrarchy +1
FROM #employees e
INNER JOIN DomainManager dm ON dm.empid = e.mgrid
)
SELECT * FROM DomainManager
WHERE func <> 'Domain Manager'
ORDER BY empid
请注意,CTE的enry / root点是Domain Manager
,因为您要查找每个员工域管理员的ID。这个id被传递到层次结构中。最终选择需要过滤掉Domain Manager
,因为您只想为每个员工提供他的ID,您不希望将他包含在结果集中。
查询结果为:
empid empname func dommgr Hyrarchy
1 Jeff Designer 4 3
2 Luke Head of designers 4 2
3 Vera Designer 4 3
答案 1 :(得分:0)
引发错误消息,因为数据包含Luke和Vera之间的循环引用。
如果添加hierarchyid字段,则执行分层查询会更容易。 SQL Server提供functions,它返回后代,祖先和层次结构中的级别。 hierarchyid
字段可以indexed,从而提高性能。
在员工示例中,您可以添加level
字段:
declare @employees table (
empid int PRIMARY KEY,
empname varchar(50),
mgrid int,
func varchar(50),
level hierarchyid not null,
INDEX IX_Level (level)
)
INSERT INTO @employees VALUES
(1, 'Jeff', 2, 'Designer' ,'/5/4/2/1/'),
(2, 'Luke', 4, 'Head of designers','/5/4/2/'),
(3, 'Vera', 2, 'Designer' ,'/5/4/2/3/'),
(4, 'Peter', 5, 'Domain Manager' ,'/5/4/'),
(5, 'Olivia', NULL, 'CEO' ,'/5/')
;
`声明@employees表( empid int PRIMARY KEY, empname varchar(50), mgrid int, func varchar(50), level hierarchyid not null, INDEX IX_Level(级别) )
INSERT INTO @employees VALUES
(1, 'Jeff', 2, 'Designer' ,'/5/4/2/1/'),
(2, 'Luke', 4, 'Head of designers','/5/4/2/'),
(3, 'Vera', 2, 'Designer' ,'/5/4/2/3/'),
(4, 'Peter', 5, 'Domain Manager' ,'/5/4/'),
(5, 'Olivia', NULL, 'CEO' ,'/5/')
;
/5/4/2/1/
是hieararchyID值的字符串表示形式。它本质上是层次结构中通向特定行的路径。
要查找域管理员的所有下属,不包括经理本身,您可以写:
with DMs as
(
select EmpID,level
from @employees
where func='Domain Manager'
)
select
PCs.empid,
PCs.empname as Name,
PCs.func as Class,
DMs.empid as DM,
PCs.level.GetLevel() as THAC0,
PCs.level.GetLevel()- DMs.level.GetLevel() as NextLevel
from
@employees PCs
inner join DMs on PCs.level.IsDescendantOf(DMs.level)=1
where DMs.EmpID<>PCs.empid;
CTE仅用于方便
结果是:
empid Name Class DM THAC0 NextLevel
1 Jeff Designer 4 4 2
2 Luke Head of designers 4 3 1
3 Vera Designer 4 4 2
CTE返回所有DM及其hierarchyid值。 IsDescendantOf()
查询检查行是否是DM的下降。 GetLevel()
返回层次结构中行的级别。通过从员工中减去DM的水平,我们得到他们之间的距离
答案 2 :(得分:0)
像其他人说的那样,你在这里有数据问题(维拉)。
IF OBJECT_ID('tempdb.dbo.#employees') IS NOT NULL
DROP TABLE #employees
CREATE TABLE #employees (
empid int,
empname varchar(50),
mgrid int,
func varchar(50)
)
INSERT INTO #employees VALUES(1, 'Jeff', 2, 'Designer')
INSERT INTO #employees VALUES(2, 'Luke', 3, 'Head of designers')
INSERT INTO #employees VALUES(3, 'Vera', 4, 'Designer') --**mgrid = 4 instead 2**
INSERT INTO #employees VALUES(4, 'Peter', 5, 'Domain Manager')
INSERT INTO #employees VALUES(5, 'Olivia', NULL, 'CEO')
;WITH Emp_CTE AS
(
SELECT empid, empname, func, mgrid AS dommgr, 0 AS Done
FROM #employees
UNION ALL
SELECT ecte.empid, ecte.empname, ecte.func,
CASE WHEN e.func = 'Domain Manager' THEN e.empid ELSE e.mgrid END AS dommgr,
CASE WHEN e.func = 'Domain Manager' THEN 1 ELSE 0 END AS Done
FROM Emp_CTE AS ecte
INNER JOIN #employees AS e ON
ecte.dommgr = e.empid
WHERE ecte.Done = 0--emp.func <> 'Domain Manager'
)
SELECT *
FROM Emp_CTE
WHERE Done = 1