【问题标题】:Sql server query to flattening a hierarchy of recordsSql server 查询以展平记录的层次结构
【发布时间】:2010-01-09 00:14:17
【问题描述】:

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

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

【问题讨论】:

  • @Jeremy:是的,您的代码可以工作,但它比像我的代码那样在临时表中包含循环变量要多做很多工作。由于每个周期必须执行 4 个选择语句,而不仅仅是一个。

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


【解决方案1】:

很遗憾你不在 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 伪代码?

更好的是,只需升级!

【讨论】:

  • @Jim : 嗯...看来我在第一行就说过了。我的答案也是公认的答案。我在问题中留下了这两个答案,因为如果有人读过这个问题,他有 2005 年(随着时间的推移,我们可以期待越来越多),他们就会知道如何在他们的 SQL 版本中做到这一点。
【解决方案2】:

这是一个 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]

【讨论】:

    【解决方案3】:

    我使用以下代码模式来遵循 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
    

    【讨论】:

      【解决方案4】:

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

      名称 MemberName 甲乙 交流电 乙丁 德 F G

      所以上面是你使用 CTE 得到的,不是

      名称 MemberName 甲乙 交流电 广告 埃 乙丁 是 德 F G

      【讨论】:

      • 我认为您需要对其进行格式化,以便清楚...缩进或使用编辑器上的代码按钮。此外,问题的顺序可能会改变,所以上面的说法不清楚——你必须使用这个人的名字。
      【解决方案5】:

      我建议您对您的数据稍作改动。您没有记录表明 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 及更高版本支持公用表表达式。

      【讨论】:

      • 我不认为 A 是根 -- 起始表中的所有元素都是根。
      • 我无法控制 HierarchyData 表的架构。我也仅限于 SQL 2000。
      猜你喜欢
      • 1970-01-01
      • 2015-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多