在RDBMS中操作 图 树 层次结构 等特殊的数据结构时,我们通常采用2个主要方法:
1.基于迭代/递归
2.具体化描述数据结构的附加信息。
一般模型有:员工组织图(树,层次结构);料表--BOM(有向图);道路系统(无向循环图)
1.迭代/递归
迭代可以迭代图的一个节点,也可以迭代一个层次.后者比前者要快很多.
实现方法:SQL2000通过UDF(用户自定义函数),SQL2005使用CTE。
a.下属问题(通俗说,求子节点)
--这里我使用书上的员工表(表内容如下)
<textarea cols="77" rows="15" name="code" class="c-sharp"> SET NOCOUNT ON; USE tempdb; GO IF OBJECT_ID('dbo.Employees') IS NOT NULL DROP TABLE dbo.Employees; GO CREATE TABLE dbo.Employees ( empid INT NOT NULL PRIMARY KEY, mgrid INT NULL REFERENCES dbo.Employees, empname VARCHAR(25) NOT NULL, salary MONEY NOT NULL, CHECK (empid <> mgrid) ); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(1, NULL, 'David', $10000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(2, 1, 'Eitan', $7000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(3, 1, 'Ina', $7500.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(4, 2, 'Seraph', $5000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(5, 2, 'Jiru', $5500.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(6, 2, 'Steve', $4500.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(7, 3, 'Aaron', $5000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(8, 5, 'Lilach', $3500.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(9, 7, 'Rita', $3000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(10, 5, 'Sean', $3000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(11, 7, 'Gabriel', $3000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(12, 9, 'Emilia' , $2000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(13, 9, 'Michael', $2000.00); INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES(14, 9, 'Didi', $1500.00); --创建索引 CREATE UNIQUE INDEX idx_unc_mgrid_empid ON dbo.Employees(mgrid, empid); go</textarea>
--SQL2000 udf方法:
<textarea cols="78" rows="15" name="code" class="css">IF OBJECT_ID('dbo.fn_subordinates1') IS NOT NULL DROP FUNCTION dbo.fn_subordinates1; GO CREATE FUNCTION dbo.fn_subordinates1(@root AS INT) RETURNS @Subs TABLE ( empid INT NOT NULL PRIMARY KEY NONCLUSTERED, lvl INT NOT NULL, UNIQUE CLUSTERED(lvl, empid) ) AS begin declare @lv int set @lv=0 insert @Subs values(@root,@lv) while @@rowcount>0 begin set @lv=@Lv+1; insert @subs select b.empid ,@Lv from @subs a join dbo.Employees b on a.empid=b.mgrid and lvl=@lv-1 end return; end go SELECT empid, lvl FROM dbo.fn_subordinates1(3) AS S; </textarea>
--SQL2005 CTE
<textarea cols="69" rows="15" name="code" class="c-sharp">DECLARE @root AS INT; SET @root = 3; WITH SubsCTE AS ( -- Anchor member returns root node SELECT empid, empname, 0 AS lvl FROM dbo.Employees WHERE empid = @root UNION ALL -- Recursive member returns next level of children SELECT C.empid, C.empname, P.lvl + 1 FROM SubsCTE AS P JOIN dbo.Employees AS C ON C.mgrid = P.empid ) SELECT * FROM SubsCTE; </textarea>
--查询结果
<textarea cols="50" rows="15" name="code" class="c-sharp">/* empid empname lvl ----------- ------------------------- ----------- 3 Ina 0 7 Aaron 1 9 Rita 2 11 Gabriel 2 12 Emilia 3 13 Michael 3 14 Didi 3 */ </textarea>
---------------如果需要限制递归的层数------------
--SQL2000
<textarea cols="79" rows="15" name="code" class="c-sharp">IF OBJECT_ID('dbo.fn_subordinates2') IS NOT NULL DROP FUNCTION dbo.fn_subordinates2; GO CREATE FUNCTION dbo.fn_subordinates2 (@root AS INT, @maxlevels AS INT = NULL) RETURNS @Subs TABLE ( empid INT NOT NULL PRIMARY KEY NONCLUSTERED, lvl INT NOT NULL, UNIQUE CLUSTERED(lvl, empid) ) AS BEGIN DECLARE @lvl AS INT; SET @lvl = 0; -- 如果@maxlevels输入的是NULL,把他变为最大的INT类型 SET @maxlevels = COALESCE(@maxlevels, 2147483647); INSERT INTO @Subs(empid, lvl) SELECT empid, @lvl FROM dbo.Employees WHERE empid = @root; WHILE @@rowcount > 0 AND @lvl < @maxlevels -- 这里注意加一个小于最大的递归数的条件,是小于 不是小于等于 BEGIN SET @lvl = @lvl + 1; INSERT INTO @Subs(empid, lvl) SELECT C.empid, @lvl --这里跟上面处理都一样 FROM @Subs AS P JOIN dbo.Employees AS C ON P.lvl = @lvl - 1 AND C.mgrid = P.empid; END RETURN; END GO SELECT empid, lvl FROM dbo.fn_subordinates2(3, NULL) AS S; </textarea>
--查询结果
<textarea cols="50" rows="15" name="code" class="c-sharp">/* empid lvl ----------- ----------- 3 0 7 1 9 2 11 2 12 3 13 3 14 3 */ -----</textarea>
SELECT empid, lvl
FROM dbo.fn_subordinates2(2, NULL) AS S;
<textarea cols="40" rows="15" name="code" class="c-sharp">/* empid lvl ----------- ----------- 2 0 4 1 5 1 6 1 8 2 10 2 */</textarea>
PS:这里控制返回的层数 你当然可以用最开始的方法,然后再筛选语句里控制层数
如SELECT empid, lvl FROM dbo.fn_subordinates1(3) AS S where lvl<3; 但是控制本身的递归只能在UDF里面了
--sql2005方法类似
<textarea cols="68" rows="15" name="code" class="c-sharp"> WITH SubsCTE AS ( SELECT empid, empname, 0 AS lvl FROM dbo.Employees WHERE empid = @root UNION ALL SELECT C.empid, C.empname, P.lvl + 1 FROM SubsCTE AS P JOIN dbo.Employees AS C ON C.mgrid = P.empid AND P.lvl < @maxlevels -- 这里控制递归数 ) SELECT * FROM SubsCTE; </textarea>
--还有一种偏方:但是不推荐 虽然结果正确 但是会报错
<textarea cols="65" rows="15" name="code" class="c-sharp">WITH SubsCTE AS ( SELECT empid, empname, 0 AS lvl FROM dbo.Employees WHERE empid = @root UNION ALL SELECT C.empid, C.empname, P.lvl + 1 FROM SubsCTE AS P JOIN dbo.Employees AS C ON C.mgrid = P.empid ) SELECT * FROM SubsCTE OPTION (MAXRECURSION 2);--就是这里的MAXRECURSION 控制递归 </textarea>
--查询结果
<textarea cols="50" rows="13" name="code" class="c-sharp"> /* empid empname lvl ----------- ------------------------- ----------- 3 Ina 0 7 Aaron 1 9 Rita 2 11 Gabriel 2 消息 530,级别 16,状态 1,第 4 行 语句被终止。完成执行语句前已用完最大递归 2。 */</textarea>
b.祖先(通俗说:求父节点)
其实思路跟子节点差不多
--SQL2000
<textarea cols="102" rows="15" name="code" class="c-sharp">IF OBJECT_ID('dbo.fn_managers') IS NOT NULL DROP FUNCTION dbo.fn_managers; GO CREATE FUNCTION dbo.fn_managers (@empid AS INT, @maxlevels AS INT = NULL) RETURNS @Mgrs TABLE ( empid INT NOT NULL PRIMARY KEY, lvl INT NOT NULL ) AS BEGIN IF NOT EXISTS(SELECT * FROM dbo.Employees WHERE empid = @empid) --这里判断是否存在经理,不存在 马上返回 RETURN; DECLARE @lvl AS INT,@mgrid int ; SET @lvl = 0; set @mgrid=@empid --赋值给@mgrid 我这是为了更加清楚变量的意思 其实不用这个@Mgrid变量的 -- 如果@maxlevels输入的是NULL,把他变为最大的INT类型 SET @maxlevels = COALESCE(@maxlevels, 2147483647); WHILE @lvl <= @maxlevels and @mgrid is not null --注意这里是小于等于 而且新插入的经理不可以为NULL,为null说明是老大了 BEGIN --插入当前经理 INSERT INTO @Mgrs(empid, lvl) VALUES(@mgrid, @lvl); --这里第一次插入是自己,后面就是各自的经理了 SET @lvl = @lvl + 1; --获得下一个级别的经理 SET @mgrid = (SELECT mgrid FROM dbo.Employees WHERE empid = @mgrid); END RETURN; END GO SELECT empid, lvl FROM dbo.fn_managers(8, NULL) AS M; </textarea>
--查询结果
<textarea cols="41" rows="13" name="code" class="c-sharp">/* empid lvl ----------- ----------- 1 3 2 2 5 1 8 0 */ </textarea>
SELECT empid, lvl
FROM dbo.fn_managers(8, 2) AS M;
<textarea cols="37" rows="10" name="code" class="c-sharp">/* empid lvl ----------- ----------- 2 2 5 1 8 0 */</textarea>
--sql2005
<textarea cols="63" rows="15" name="code" class="c-sharp">DECLARE @empid AS INT, @maxlevels AS INT; SET @empid = 8; SET @maxlevels = 2; WITH MgrsCTE AS ( SELECT empid, mgrid, empname, 0 AS lvl FROM dbo.Employees WHERE empid = @empid UNION ALL SELECT P.empid, P.mgrid, P.empname, C.lvl + 1 FROM MgrsCTE AS C JOIN dbo.Employees AS P ON C.mgrid = P.empid AND C.lvl < @maxlevels--限制递归数... ) SELECT * FROM MgrsCTE; </textarea>
C.层次显示
--SQL2000,思路跟下属问题一模一样 只是多了个PATH
<textarea cols="86" rows="15" name="code" class="c-sharp">IF OBJECT_ID('dbo.fn_subordinates3') IS NOT NULL DROP FUNCTION dbo.fn_subordinates3; GO CREATE FUNCTION dbo.fn_subordinates3 (@root AS INT, @maxlevels AS INT = NULL) RETURNS @Subs TABLE ( empid INT NOT NULL PRIMARY KEY NONCLUSTERED, lvl INT NOT NULL, path VARCHAR(900) NOT NULL UNIQUE CLUSTERED(lvl, empid) ) AS BEGIN DECLARE @lvl AS INT; SET @lvl = 0; SET @maxlevels = COALESCE(@maxlevels, 2147483647); INSERT INTO @Subs(empid, lvl, path) SELECT empid, @lvl, CAST(empid AS VARCHAR(100))--ZHU YI ZHE LI FROM dbo.Employees WHERE empid = @root; WHILE @@rowcount > 0 AND @lvl < @maxlevels BEGIN SET @lvl = @lvl + 1; INSERT INTO @Subs(empid, lvl, path) SELECT C.empid, @lvl, P.path +'-'+ CAST(C.empid AS VARCHAR(100)) --和上面类型保持一致 FROM @Subs AS P JOIN dbo.Employees AS C ON P.lvl = @lvl - 1 AND C.mgrid = P.empid; END RETURN; END GO </textarea>
--这里的显示分2种
--显示1
select empid ,pos=REPLICATE('-',lvl)+rtrim(empid)
FROM dbo.fn_subordinates3(1, NULL) AS S
ORDER BY PATH
<textarea cols="50" rows="15" name="code" class="c-sharp">/* empid pos ----------- ------------ 1 1 2 -2 4 --4 5 --5 10 ---10 8 ---8 6 --6 3 -3 7 --7 11 ---11 9 ---9 12 ----12 13 ----13 14 ----14 */</textarea>
--还有一种
SELECT empid, path
FROM dbo.fn_subordinates3(1, NULL) AS S
ORDER BY PATH
<textarea cols="50" rows="15" name="code" class="c-sharp">/* empid path ----------- ---------------- 1 1 2 1-2 4 1-2-4 5 1-2-5 10 1-2-5-10 8 1-2-5-8 6 1-2-6 3 1-3 7 1-3-7 11 1-3-7-11 9 1-3-7-9 12 1-3-7-9-12 13 1-3-7-9-13 14 1-3-7-9-14 */</textarea>
--当然上面的显示方式还很多,自己可以控制 比如不加ORDER 就可以排出不一样的
--SQL2005
<textarea cols="71" rows="15" name="code" class="c-sharp">DECLARE @root AS INT; SET @root = 1; WITH SubsCTE AS ( SELECT empid, empname, 0 AS lvl, -- Path of root = '.' + empid + '.' CAST(CAST(empid AS VARCHAR(10)) AS VARCHAR(MAX)) AS path FROM dbo.Employees WHERE empid = @root UNION ALL SELECT C.empid, C.empname, P.lvl + 1, CAST(P.path+'-' + CAST(C.empid AS VARCHAR(10)) AS VARCHAR(MAX)) AS path FROM SubsCTE AS P JOIN dbo.Employees AS C ON C.mgrid = P.empid ) SELECT empid, REPLICATE(' | ', lvl) + empname AS empname FROM SubsCTE ORDER BY path </textarea> ;
--结果查询
<textarea cols="50" rows="15" name="code" class="c-sharp">/* empid empname ----------- ------------------------- 1 David 2 | Eitan 4 | | Seraph 5 | | Jiru 10 | | | Sean 8 | | | Lilach 6 | | Steve 3 | Ina 7 | | Aaron 11 | | | Gabriel 9 | | | Rita 12 | | | | Emilia 13 | | | | Michael 14 | | | | Didi */ </textarea>
D.检测循环中异常
说白了 就是检查表里有没出现1-2-4-1这种头接尾的圈.这在现实中是不可能的.一个经理部可能是它手下的手下.-- ||
我们对表做手脚,把老大的经理改成是它的一个员工
<textarea cols="70" rows="15" name="code" class="c-sharp">UPDATE dbo.Employees SET mgrid = 14 WHERE empid = 1; DECLARE @root AS INT; SET @root = 1; WITH SubsCTE AS ( SELECT empid, empname, 0 AS lvl, CAST( CAST(empid AS VARCHAR(10)) AS VARCHAR(MAX)) AS path, -- 第一层是不会出现圈圈的 0 AS cycle --0表示没有圈 1表示出现圈 FROM dbo.Employees WHERE empid = @root UNION ALL SELECT C.empid, C.empname, P.lvl + 1, CAST(P.path + '-'+CAST(C.empid AS VARCHAR(10)) AS VARCHAR(MAX)) AS path, -- 这里需要检查 CASE WHEN P.path LIKE '%' + CAST(C.empid AS VARCHAR(10)) + '%' THEN 1 ELSE 0 END FROM SubsCTE AS P JOIN dbo.Employees AS C ON C.mgrid = P.empid where p.cycle=0 --保证不会继续在错误的地方继续循环下去 ) SELECT empid, empname, cycle, path FROM SubsCTE where cycle=1 </textarea>
--查询结果
<textarea cols="82" rows="14" name="code" class="c-sharp">/* empid empname cycle path ----------- ------------------------- ----------- ------------------ 1 David 1 1-3-7-9-14-1 */ </textarea>
--这样管理员可以轻易找到那个错误的地方.
改过来:UPDATE dbo.Employees SET mgrid = NULL WHERE empid = 1;
2.具体化路径
这里就是新增加2列,一列是级别 一列是节点路径,这样可以避免每次都去计算.
2个优点:不需要递归,只需要基于集合 ;查询可以使用到路径索引
a.维护数据
1.添加不管理员工的员工
<textarea cols="105" rows="15" name="code" class="c-sharp">SET NOCOUNT ON; USE tempdb; GO IF OBJECT_ID('dbo.Employees') IS NOT NULL DROP TABLE dbo.Employees; GO CREATE TABLE dbo.Employees ( empid INT NOT NULL PRIMARY KEY NONCLUSTERED, mgrid INT NULL REFERENCES dbo.Employees, empname VARCHAR(25) NOT NULL, salary MONEY NOT NULL, lvl INT NOT NULL, path VARCHAR(900) NOT NULL UNIQUE CLUSTERED ); CREATE UNIQUE INDEX idx_unc_mgrid_empid ON dbo.Employees(mgrid, empid); GO IF OBJECT_ID('dbo.usp_insertemp') IS NOT NULL DROP PROC dbo.usp_insertemp; GO CREATE PROC dbo.usp_insertemp @empid INT, @mgrid INT, @empname VARCHAR(25), @salary MONEY AS SET NOCOUNT ON; -- 如果插入的是一个没有经理的老大 IF @mgrid IS NULL INSERT INTO dbo.Employees(empid, mgrid, empname, salary, lvl, path) VALUES(@empid, @mgrid, @empname, @salary, 0, CAST(@empid AS VARCHAR(10))); --插入有经理的小弟 ELSE INSERT INTO dbo.Employees(empid, mgrid, empname, salary, lvl, path) SELECT @empid, @mgrid, @empname, @salary, lvl + 1, path +'-'+ CAST(@empid AS VARCHAR(10)) FROM dbo.Employees WHERE empid = @mgrid; GO EXEC dbo.usp_insertemp @empid = 1, @mgrid = NULL, @empname = 'David', @salary = $10000.00; EXEC dbo.usp_insertemp @empid = 2, @mgrid = 1, @empname = 'Eitan', @salary = $7000.00; EXEC dbo.usp_insertemp @empid = 3, @mgrid = 1, @empname = 'Ina', @salary = $7500.00; EXEC dbo.usp_insertemp @empid = 4, @mgrid = 2, @empname = 'Seraph', @salary = $5000.00; EXEC dbo.usp_insertemp @empid = 5, @mgrid = 2, @empname = 'Jiru', @salary = $5500.00; EXEC dbo.usp_insertemp @empid = 6, @mgrid = 2, @empname = 'Steve', @salary = $4500.00; EXEC dbo.usp_insertemp @empid = 7, @mgrid = 3, @empname = 'Aaron', @salary = $5000.00; EXEC dbo.usp_insertemp @empid = 8, @mgrid = 5, @empname = 'Lilach', @salary = $3500.00; EXEC dbo.usp_insertemp @empid = 9, @mgrid = 7, @empname = 'Rita', @salary = $3000.00; EXEC dbo.usp_insertemp @empid = 10, @mgrid = 5, @empname = 'Sean', @salary = $3000.00; EXEC dbo.usp_insertemp @empid = 11, @mgrid = 7, @empname = 'Gabriel', @salary = $3000.00; EXEC dbo.usp_insertemp @empid = 12, @mgrid = 9, @empname = 'Emilia', @salary = $2000.00; EXEC dbo.usp_insertemp @empid = 13, @mgrid = 9, @empname = 'Michael', @salary = $2000.00; EXEC dbo.usp_insertemp @empid = 14, @mgrid = 9, @empname = 'Didi', @salary = $1500.00 </textarea> ;
----检测
SELECT empid, mgrid, empname, salary, lvl, path
FROM dbo.Employees
ORDER BY path;
<textarea cols="104" rows="15" name="code" class="c-sharp">/* empid mgrid empname salary lvl path ----------- ----------- ------------------------- --------------------- ----------- --------- 1 NULL David 10000.00 0 1 2 1 Eitan 7000.00 1 1-2 4 2 Seraph 5000.00 2 1-2-4 5 2 Jiru 5500.00 2 1-2-5 10 5 Sean 3000.00 3 1-2-5-10 8 5 Lilach 3500.00 3 1-2-5-8 6 2 Steve 4500.00 2 1-2-6 3 1 Ina 7500.00 1 1-3 7 3 Aaron 5000.00 2 1-3-7 11 7 Gabriel 3000.00 3 1-3-7-11 9 7 Rita 3000.00 3 1-3-7-9 12 9 Emilia 2000.00 4 1-3-7-9-12 13 9 Michael 2000.00 4 1-3-7-9-13 14 9 Didi 1500.00 4 1-3-7-9-14 */ </textarea>
2.移动子树
比如说某个部门来了个新老大,原来部门老大和手下的人都要跟着他.这个时候表里的路径和级别都要更新
Select Empid, Replicate(' | ', Lvl) + Empname As Empname, Lvl, Path
From Dbo.Employees
Order By Path;
--这个是移动之前的层次分布
<textarea cols="89" rows="15" name="code" class="c-sharp">/* empid empname lvl path ----------- ----------------- ----------- ------------ 1 David 0 1 2 | Eitan 1 1-2 4 | | Seraph 2 1-2-4 5 | | Jiru 2 1-2-5 10 | | | Sean 3 1-2-5-10 6 | | Steve 2 1-2-6 3 | Ina 1 1-3 7 | | Aaron 2 1-3-7 11 | | | Gabriel 3 1-3-7-11 9 | | | Rita 3 1-3-7-9 13 | | | | Michael 4 1-3-7-9-13 14 | | | | Didi 4 1-3-7-9-14 */ </textarea>
==接下来我们移动
<textarea cols="96" rows="15" name="code" class="c-sharp">IF OBJECT_ID('dbo.usp_movesubtree') IS NOT NULL DROP PROC dbo.usp_movesubtree; GO CREATE PROC dbo.usp_movesubtree @root INT,--旧经理 @mgrid INT--新经理 AS SET NOCOUNT ON; BEGIN TRAN; UPDATE E SET lvl = E.lvl + NM.lvl - OM.lvl,-- 级别=当前的级别+(新经理级别-原经理级别) path = STUFF(E.path, 1, LEN(OM.path), NM.path) -- 路径=自己的路径移除原来经理那部分再加上新的经理的那部分 --这里的 OM.path 是被替换经理的经理的路径 比如旧经理是1-3-7 那么OM.PATH 就是1-3 --NM.path 是新经理的路径了 比如要换新经理的EMPID是10 所以NM.path 就是 1-2-5-10 --所以如果本来旧经理一个手下比如说是1-3-7-10 现在整个替换过来就是1-2-5-10-7-10 FROM dbo.Employees AS E -- E = 员工 JOIN dbo.Employees AS R -- R = 根 ON R.empid = @root AND E.path LIKE R.path + '%' JOIN dbo.Employees AS OM -- OM = 旧经理 ON OM.empid = R.mgrid JOIN dbo.Employees AS NM -- NM = 新经理 ON NM.empid = @mgrid; -- 更新旧的经理的经理为新来的经理 UPDATE dbo.Employees SET mgrid = @mgrid WHERE empid = @root; COMMIT TRAN; GO BEGIN TRAN; EXEC dbo.usp_movesubtree @root = 7, @mgrid = 10; -- After moving subtree SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path FROM dbo.Employees ORDER BY path; ROLLBACK TRAN; </textarea>
--移动后结果
<textarea cols="96" rows="15" name="code" class="c-sharp">/* empid empname lvl path ----------- ------------------------------ ----------- ---------------------- 1 David 0 1 2 | Eitan 1 1-2 4 | | Seraph 2 1-2-4 5 | | Jiru 2 1-2-5 10 | | | Sean 3 1-2-5-10 7 | | | | Aaron 4 1-2-5-10-7 11 | | | | | Gabriel 5 1-2-5-10-7-11 9 | | | | | Rita 5 1-2-5-10-7-9 12 | | | | | | Emilia 6 1-2-5-10-7-9-12 13 | | | | | | Michael 6 1-2-5-10-7-9-13 14 | | | | | | Didi 6 1-2-5-10-7-9-14 8 | | | Lilach 3 1-2-5-8 6 | | Steve 2 1-2-6 3 | Ina 1 1-3 */ </textarea>
--这个是移动之后 大家注意观察7 和 10 两位同志
3.移除子树
就是把某个部门从公司取消掉
--首先查看公司部门当前分布
<textarea cols="55" rows="8" name="code" class="c-sharp">SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path FROM dbo.Employees ORDER BY path;</textarea>
--查询结果
<textarea cols="83" rows="15" name="code" class="c-sharp">/* empid empname lvl path ----------- ------------- ------------- ----------- 1 David 0 1 2 | Eitan 1 1-2 4 | | Seraph 2 1-2-4 5 | | Jiru 2 1-2-5 8 | | | Lilach 3 1-2-5-8 6 | | Steve 2 1-2-6 3 | Ina 1 1-3 7 | | Aaron 2 1-3-7 11 | | | Gabriel 3 1-3-7-11 9 | | | Rita 3 1-3-7-9 12 | | | | Emilia 4 1-3-7-9-12 13 | | | | Michael 4 1-3-7-9-13 14 | | | | Didi 4 1-3-7-9-14 */ </textarea>
--接着我们来开始一个部门 比如移除Aaron手下的人
<textarea cols="83" rows="15" name="code" class="c-sharp">BEGIN TRAN; DELETE FROM dbo.Employees WHERE path LIKE (SELECT M.path + '%' FROM dbo.Employees as M WHERE M.empid = 7); --删除之后显示 SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path FROM dbo.Employees ORDER BY path; ROLLBACK TRAN; </textarea>
--查询结果
<textarea cols="80" rows="15" name="code" class="c-sharp">/* empid empname lvl path ----------- ------------------ ----------- ------------ 1 David 0 1 2 | Eitan 1 1-2 4 | | Seraph 2 1-2-4 5 | | Jiru 2 1-2-5 10 | | | Sean 3 1-2-5-10 8 | | | Lilach 3 1-2-5-8 6 | | Steve 2 1-2-6 3 | Ina 1 1-3 */ </textarea>
4.查询
这里的查询可就轻松多啦 ~因为路径都有啦~
--查询EMPID为3的手下一批人
<textarea cols="75" rows="15" name="code" class="c-sharp">SELECT REPLICATE(' | ', E.lvl - M.lvl) + E.empname FROM dbo.Employees AS E JOIN dbo.Employees AS M ON M.empid = 3 -- root AND E.path LIKE M.path + '%' --and E.path LIKE M.path + '_%'--这里那个3号就没了 --and E.lvl - M.lvl <= 2 --限制级数 --WHERE NOT EXISTS --自己本身不是经理的,返回的都是员工 (SELECT * FROM dbo.Employees AS E2 WHERE E2.mgrid = E.empid); ORDER BY E.path;</textarea>
--查询结果
<textarea cols="50" rows="15" name="code" class="c-sharp">/* ------------------ Ina | Aaron | | Gabriel | | Rita | | | Emilia | | | Michael | | | Didi */</textarea>
b.嵌套集合
是书的作者认为用于树建模最完美最高明的解决方案.
惭愧..太难..准备翻第二次书的时候再研究...