Sql server查询以展平记录层次结构

时间:2010-01-09 00:14:18

标签: sql-server sql-server-2000 hierarchy

我有一个描述层次结构的表:

Name    MemberName
A       B
A       C
B       D
D       E
F       G

MemberName引用同一个表的Name列。从这张表中,我可以轻松查询B和C是A中的成员,D是B的成员,E是D的成员,G是F的成员。

基于这种结构,很难编写一个查询,表明D和E也间接成为A的成员.D和E也间接成为B的成员,等等。所以我需要做的是建立显示所有间接成员的新表。因此,对于上面的表数据,我最终会得到一个包含以下内容的表:

Name    MemberName
A       B
A       C
A       D
A       E
B       D
B       E
D       E
F       G

我首先将所有不是其他记录(顶级)记录成员的记录放入临时表中:

CREATE TABLE #TMP
(
    [Name] varchar(20),
    [MemberName] varchar(20)
)

DECLARE @iRowsFound INT
INSERT INTO #TMP ([Name],[MemberName]) 
(SELECT * FROM [HierarchyData] WHERE [Name] NOT IN 
   (SELECT [MemberName] FROM [HierarchyData]))
SELECT @iRowsFound = @@ROWCOUNT

Name    MemberName
A       B
A       C
F       G

然后我的理论是,在一个while循环中,将临时表交叉连接到heirachy表,并将来自交叉连接的适用记录插回到临时表中,并执行while循环,直到没有更多适用的记录在交叉连接中插入:

WHILE (@iRowsFound > 0)
BEGIN
    INSERT INTO #TMP ([Name],[MemberName]) 
    (
        SELECT 
            [NewName] = ??,
            [NewMember] = ??
        FROM
            [HierarchyData],[#TMP]
        WHERE
            ???        
    )
    SELECT @iRowsFound = @@ROWCOUNT
END

我只是不确定自己是否在正确的轨道上,因为我对交叉连接选择应该是什么感觉有点难过。有没有人做过这样的事情(在sql server 2000中)?

编辑:我想我可能已经知道了: - 虽然我很确定必须有 更有效的方法......

WHILE (@iRowsFound > 0)
BEGIN
    INSERT INTO #TMP ([Name],[MemberName]) 
    (       
            SELECT
                --[#TMP].[Name],
                --[#TMP].[MemberName],
                [HierarchyData].[Name],
                [HierarchyData].[MemberName]
            FROM 
                [#TMP]
            JOIN 
                [HierarchyData] ON [#TMP].[MemberName] = [HierarchyData].[Name]
            --WHERE
            --  [#TMP].[MemberName] = [HierarchyData].[Name]
            AND NOT EXISTS (SELECT * FROM [#TMP] WHERE [#TMP].[Name] = [HierarchyData].[Name] AND [#TMP].[MemberName] = [HierarchyData].[MemberName])   
            UNION   
            SELECT
                [#TMP].[Name],
                --[#TMP].[MemberName],
                --[HierarchyData].[Name],
                [HierarchyData].[MemberName]
            FROM 
                [#TMP]
            JOIN 
                [HierarchyData] ON [#TMP].[MemberName] = [HierarchyData].[Name]     
            AND NOT EXISTS (SELECT * FROM [#TMP] WHERE [#TMP].[Name] = [#TMP].[Name] AND [#TMP].[MemberName] = [HierarchyData].[MemberName])    

    )
    SELECT @iRowsFound = @@ROWCOUNT
END

5 个答案:

答案 0 :(得分:6)

很遗憾你不在sql server 2005或更高版本上,使用递归CTE很容易,代码就在这里:

WITH Members AS
(
  Select Name, MemberName 
  FROM HierarchyData
  UNION ALL
  SELECT Name, Child.MemberName as [MemberName]
  FROM Members
  JOIN HierarchyData Child ON Members.MemberName = Child.Name
)
SELECT * FROM Members

在2000年你可以基本上以相同的方式做到(将最后一个选择的结果加到原始表中,直到你没有循环中最后一个结果的结果),但它要困难得多,因为你必须跟踪您通过计数器进行的迭代。呸。

这有用吗,或者你想要一些sql 2000伪代码吗?

更好的是,只需升级!

答案 1 :(得分:3)

这是一个SQL 2000版本。

一些注意事项:这将适用于任意数量的级别,并且不会出现循环错误(如CTE版本所示)。

declare @lastcount int
declare @lastcycle int

Select HierarchyData.Name, HierarchyData.MemberName, 0 as [Cycle] INTO #list
FROM HierarchyData

SET @lastcount = @@rowcount
SET @lastcycle = 0

while @lastcount > 0
BEGIN
  INSERT INTO #list
    SELECT Members.Name, Child.MemberName as [MemberName], @lastcycle+1 as [Cycle]
    FROM #list Members
    JOIN HierarchyData Child ON Members.MemberName = Child.Name
    LEFT JOIN #list cycletest ON Members.Name = cycletest.Name AND Child.MemberName = cycletest.Membername
    WHERE Members.Cycle = @lastcycle AND NOT (Members.Name = Child.MemberName) AND cycletest.Name is null

  SET @lastcount = @@rowcount

  SET @lastcycle = @lastcycle + 1
END

SELECT [Name], [MemberName] FROM #list
ORDER BY [Name], [MemberName]

DROP TABLE #list

---- Test data
--create table HierarchyData
--(
--  [Name] varchar(20),
--  [MemberName] varchar(20)
--)
--
--INSERT INTO HierarchyData (Name,MemberName) Values('A','B')
--INSERT INTO HierarchyData (Name,MemberName) Values('A','C')
--INSERT INTO HierarchyData (Name,MemberName) Values('B','D')
--INSERT INTO HierarchyData (Name,MemberName) Values('D','E')
--INSERT INTO HierarchyData (Name,MemberName) Values('F','G')
----CYCLE TEST  (the CTE will not work)
--INSERT INTO HierarchyData (Name,MemberName) Values('E','D')
--
---- Test
--select * from HierarchyData

---- CTE Works (note, will fail on cycles.)
--WITH Members AS
--(
--  Select HierarchyData.Name, HierarchyData.MemberName 
--  FROM HierarchyData
--  UNION ALL
--  SELECT Members.Name, Child.MemberName as [MemberName]
--  FROM Members
--  JOIN HierarchyData Child ON Members.MemberName = Child.Name
--)
--SELECT * FROM Members
--ORDER BY [Name], [MemberName]

答案 2 :(得分:1)

我使用以下代码模式来遵循SQL Server 2000中的层次结构。“魔术”是将深度值添加到临时表中,以便您可以在WHERE子句中使用它。

SET NOCOUNT ON

CREATE TABLE #super_trees
(
    supervisor_uid  INTEGER,
    actor_uid       INTEGER,
    depth           INTEGER
)

DECLARE
    @more_users BIT,
    @depth      INTEGER

SET @more_users = 1
SET @depth      = 0

INSERT INTO #super_trees VALUES (@supervisor_uid, @supervisor_uid, @depth)

SET @depth = @depth + 1

WHILE (@more_users = 1)
BEGIN

    INSERT INTO #super_trees (supervisor_uid, actor_uid, depth)
        SELECT u.supervisor_uid,
               u.actor_uid,
               @depth
          FROM #super_trees sr
           INNER JOIN
           dbo.users u
           ON (sr.actor_uid = u.supervisor_uid)
         WHERE sr.depth = (@depth - 1)

    IF @@ROWCOUNT < 1
        SET @more_users = 0

    SET @depth = @depth + 1

END

答案 3 :(得分:0)

使用上述CTE不符合海报的目的。他/她想要平整数据。 CTE仅返回ParentID列下具有不同值的层次结构信息。

名称会员名称 一个B. 一个C. B D. D E. F G

所以上面是你使用CTE,而不是

名称会员名称 一个B. 一个C. 广告 一个E. B D. B E. D E. F G

答案 4 :(得分:-1)

我建议你稍微改动一下你的数据。您没有记录表明A是层次结构的根。补充一点:

INSERT INTO #TMP(Name, MemberName) VALUES (NULL, 'A') 

极大地简化了事情(通常,邻接列表将以“反过来”的方式表示:列Name和列ParentName,它们对应于您的MemberNameName列。

使用该设置,您可以使用公用表表达式来完成这项工作:

WITH Node (Name, ParentName)
AS  (
    SELECT     Name, ParentName
    FROM       Tab
    WHERE      ParentName IS NULL
    UNION ALL
    SELECT     Tab.Name, Tab.ParentName
    FROM       Tab
    INNER JOIN Node
    ON         ParentName = Node.Name
    )
SELECT Name, ParentName
FROM   Node

不幸的是,正如Hogan所指出的那样,MS SQL 2005中支持公共表表达式。