在RDBMS中操作 图 树 层次结构 等特殊的数据结构时,我们通常采用2个主要方法:
1.基于迭代/递归
2.具体化描述数据结构的附加信息。
一般模型有:员工组织图(树,层次结构);料表--BOM(有向图);道路系统(无向循环图)
1.迭代/递归
迭代可以迭代图的一个节点,也可以迭代一个层次.后者比前者要快很多.
实现方法:SQL2000通过UDF(用户自定义函数),SQL2005使用CTE。
a.下属问题(通俗说,求子节点)
--这里我使用书上的员工表(表内容如下)
view plaincopy to clipboardprint?
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
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
--SQL2000 udf方法:
view plaincopy to clipboardprint?
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;
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;
--SQL2005 CTE
view plaincopy to clipboardprint?
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;
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;
--查询结果
view plaincopy to clipboardprint?
/*
empid empname lvl
----------- ------------------------- -----------
3 Ina 0
7 Aaron 1
9 Rita 2
11 Gabriel 2
12 Emilia 3
13 Michael 3
14 Didi 3
*/
/*
empid empname lvl
----------- ------------------------- -----------
3 Ina 0
7 Aaron 1
9 Rita 2
11 Gabriel 2
12 Emilia 3
13 Michael 3
14 Didi 3
*/
---------------如果需要限制递归的层数------------
--SQL2000
view plaincopy to clipboardprint?
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;
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;
--查询结果
view plaincopy to clipboardprint?
/*
empid lvl
----------- -----------
3 0
7 1
9 2
11 2
12 3
13 3
14 3
*/
-----
/*
empid lvl
----------- -----------
3 0
7 1
9 2
11 2
12 3
13 3
14 3
*/
-----
SELECT empid, lvl
FROM dbo.fn_subordinates2(2, NULL) AS S;
view plaincopy to clipboardprint?
/*
empid lvl
----------- -----------
2 0
4 1
5 1
6 1
8 2
10 2
*/
/*
empid lvl
----------- -----------
2 0
4 1
5 1
6 1
8 2
10 2
*/
PS:这里控制返回的层数 你当然可以用最开始的方法,然后再筛选语句里控制层数
如SELECT empid, lvl FROM dbo.fn_subordinates1(3) AS S where lvl<3; 但是控制本身的递归只能在UDF里面了
--sql2005方法类似
view plaincopy to clipboardprint?
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;
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;
--还有一种偏方:但是不推荐 虽然结果正确 但是会报错
view plaincopy to clipboardprint?
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 控制递归
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 控制递归
--查询结果
view plaincopy to clipboardprint?
/*
empid empname lvl
----------- ------------------------- -----------
3 Ina 0
7 Aaron 1
9 Rita 2
11 Gabriel 2
消息 530,级别 16,状态 1,第 4 行
语句被终止。完成执行语句前已用完最大递归 2。
*/
/*
empid empname lvl
----------- ------------------------- -----------
3 Ina 0
7 Aaron 1
9 Rita 2
11 Gabriel 2
消息 530,级别 16,状态 1,第 4 行
语句被终止。完成执行语句前已用完最大递归 2。
*/
b.祖先(通俗说:求父节点)
其实思路跟子节点差不多
--SQL2000
view plaincopy to clipboardprint?
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;
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;
--查询结果
view plaincopy to clipboardprint?
/*
empid lvl
----------- -----------
1 3
2 2
5 1
8 0
*/
/*
empid lvl
----------- -----------
1 3
2 2
5 1
8 0
*/
SELECT empid, lvl
FROM dbo.fn_managers(8, 2) AS M;
view plaincopy to clipboardprint?
/*
empid lvl
----------- -----------
2 2
5 1
8 0
*/
/*
empid lvl
----------- -----------
2 2
5 1
8 0
*/
--sql2005
view plaincopy to clipboardprint?
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;
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;
C.层次显示
--SQL2000,思路跟下属问题一模一样 只是多了个PATH
view plaincopy to clipboardprint?
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
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
--这里的显示分2种
--显示1
select empid ,pos=REPLICATE('-',lvl)+rtrim(empid)
FROM dbo.fn_subordinates3(1, NULL) AS S
ORDER BY PATH
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
--还有一种
SELECT empid, path
FROM dbo.fn_subordinates3(1, NULL) AS S
ORDER BY PATH
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
--当然上面的显示方式还很多,自己可以控制 比如不加ORDER 就可以排出不一样的
--SQL2005
view plaincopy to clipboardprint?
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
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
;
--结果查询
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
D.检测循环中异常
说白了 就是检查表里有没出现1-2-4-1这种头接尾的圈.这在现实中是不可能的.一个经理部可能是它手下的手下.-- ||
我们对表做手脚,把老大的经理改成是它的一个员工
view plaincopy to clipboardprint?
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
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
--查询结果
view plaincopy to clipboardprint?
/*
empid empname cycle path
----------- ------------------------- ----------- ------------------
1 David 1 1-3-7-9-14-1
*/
/*
empid empname cycle path
----------- ------------------------- ----------- ------------------
1 David 1 1-3-7-9-14-1
*/
--这样管理员可以轻易找到那个错误的地方.
改过来:UPDATE dbo.Employees SET mgrid = NULL WHERE empid = 1;
2.具体化路径
这里就是新增加2列,一列是级别 一列是节点路径,这样可以避免每次都去计算.
2个优点:不需要递归,只需要基于集合 ;查询可以使用到路径索引
a.维护数据
1.添加不管理员工的员工
view plaincopy to clipboardprint?
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
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
;
----检测
SELECT empid, mgrid, empname, salary, lvl, path
FROM dbo.Employees
ORDER BY path;
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
2.移动子树
比如说某个部门来了个新老大,原来部门老大和手下的人都要跟着他.这个时候表里的路径和级别都要更新
Select Empid, Replicate(' | ', Lvl) + Empname As Empname, Lvl, Path
From Dbo.Employees
Order By Path;
--这个是移动之前的层次分布
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
==接下来我们移动
view plaincopy to clipboardprint?
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;
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;
--移动后结果
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
--这个是移动之后 大家注意观察7 和 10 两位同志
3.移除子树
就是把某个部门从公司取消掉
--首先查看公司部门当前分布
view plaincopy to clipboardprint?
SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
FROM dbo.Employees
ORDER BY path;
SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
FROM dbo.Employees
ORDER BY path;
--查询结果
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
--接着我们来开始一个部门 比如移除Aaron手下的人
view plaincopy to clipboardprint?
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;
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;
--查询结果
view plaincopy to clipboardprint?
/*
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
*/
/*
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
*/
4.查询
这里的查询可就轻松多啦 ~因为路径都有啦~
--查询EMPID为3的手下一批人
view plaincopy to clipboardprint?
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;
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;
--查询结果
view plaincopy to clipboardprint?
/*
------------------
Ina
| Aaron
| | Gabriel
| | Rita
| | | Emilia
| | | Michael
| | | Didi
*/
/*
------------------
Ina
| Aaron
| | Gabriel
| | Rita
| | | Emilia
| | | Michael
| | | Didi
*/
b.嵌套集合
是书的作者认为用于树建模最完美最高明的解决方案.
惭愧..太难..准备翻第二次书的时候再研究...
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/feixianxxx/archive/2009/11/01/4753783.aspx