【问题标题】:Finding breadcrumbs for nested sets查找嵌套集的面包屑
【发布时间】:2010-10-22 23:42:20
【问题描述】:

我正在使用嵌套集(又名修改的预排序树遍历)来存储组列表,并且我正在尝试找到一种快速的方法来为所有组生成面包屑(作为字符串,而不是表格)立刻。我的数据也使用邻接列表模型存储(有触发器可以使两者保持同步)。

例如:

ID   Name    ParentId  Left   Right
0    Node A  0         1      12
1    Node B  0         2      5
2    Node C  1         3      4
3    Node D  0         6      11
4    Node E  3         7      8
5    Node F  4         9      9

代表树:

  • 节点 A
    • 节点 B
      • 节点 C
    • 节点 D
      • 节点 E
      • 节点 F

我希望能够有一个返回表格的用户定义函数:

ID  Breadcrumb
0   Node A
1   Node A > Node B
2   Node A > Node B > Node C
3   Node A > Node D
4   Node A > Node D > Node E
5   Node A > Node D > Node F

为了让这件事稍微复杂一些(尽管它有点超出了问题的范围),我还有一些需要遵守的用户限制。例如,如果我只能访问 id=3,当我运行查询时,我应该得到:

ID  Breadcrumb
3   Node D
4   Node D > Node E
5   Node D > Node F

我确实有一个用户定义的函数,它以用户 ID 作为参数,并返回一个表,其中包含所有有效组的 ID,只要在查询中的某个位置

WHERE group.id IN (SELECT id FROM dbo.getUserGroups(@userid))

它会起作用的。


我有一个现有的标量函数可以做到这一点,但它不适用于任何合理数量的组(2000 个组需要 >10 秒)。它以 groupid 和 userid 作为参数,并返回一个 nvarchar。它找到给定的组父母(1个查询来获取左/右值,另一个查询来查找父母),将列表限制为用户有权访问的组(使用与上面相同的 WHERE 子句,所以还有另一个查询),然后使用游标遍历每个组并将其附加到字符串,最后返回该值。

我需要一种可以快速运行的方法(例如

这是在 SQL Server 2005 上。

【问题讨论】:

    标签: sql sql-server-2005 breadcrumbs nested-sets mptt


    【解决方案1】:

    如果可以,请使用路径(或者我想我听说它被称为沿袭)字段,例如:

    ID   Name    ParentId  Left   Right   Path
    0    Node A  0         1      12      0,
    1    Node B  0         2      5       0,1,
    2    Node C  1         3      4       0,1,2,
    3    Node D  0         6      11      0,3,
    4    Node E  3         7      8       0,3,4,
    5    Node F  4         9      9       0,3,4,
    

    仅获取节点 D 及以后(伪代码):

    path = SELECT Path FROM Nodes WHERE ID = 3
    SELECT * FROM Nodes WHERE Path LIKE = path + '%'
    

    【讨论】:

      【解决方案2】:

      没有特定于 sql server 的代码,但您只是在寻找:

      SELECT * FROM table WHERE left (currentid.right)

      【讨论】:

      • 这仅适用于特定节点(currentid),并返回一个表,而不是一个字符串。此外,它应该是 = 来包含节点,并且 ORDER BY left 以将它们按层次结构顺序排列。例如:对于“Node F”,id 5,它将返回:0 Node A 3 Node D 4 Node F 那部分,我知道该怎么做。我想将“节点 A > 节点 D > 节点 F”作为字段返回,并在一个大查询中为每个组执行此操作。
      • 嗯,SQL server 技能还不够,我可能会在我的数据库之外处理它。
      【解决方案3】:

      我最终做的是建立一个大型连接,简单地将这个表与自身联系起来,一遍又一遍地为每个级别。

      首先我用第一级组填充一个表@topLevelGroups(如果你只有一个根,你可以跳过这一步),然后用用户可以看到的组填充@userGroups。

      SELECT groupid,
         (level1 
          + CASE WHEN level2 IS NOT NULL THEN ' > ' + level2 ELSE '' END
          + CASE WHEN level3 IS NOT NULL THEN ' > ' + level3 ELSE '' END
         )as [breadcrumb]
      FROM (
        SELECT g3.*
          ,g1.name as level1
          ,g2.name as level2
          ,g3.name as level3
        FROM @topLevelGroups g1
        INNER JOIN @userGroups g2 ON g2.parentid = g1.groupid and g2.groupid <> g1.groupid
        INNER JOIN @userGroups g3 ON g3.parentid = g2.groupid 
      
        UNION
      
        SELECT g2.*
          ,g1.name as level1
          ,g2.name as level2
          ,NULL as level3
        FROM @topLevelGroups g1 
        INNER JOIN @userGroups g2 ON g2.parentid = g1.groupid and g2.groupid <> g1.groupid
      
        UNION
      
        SELECT g1.*
          ,g1.name as level1
          ,NULL as level2
          ,NULL as level3 
        FROM @topLevelGroups g1
      
      ) a
      ORDER BY [breadcrumb]
      

      这是一个相当大的 hack,显然仅限于一定数量的级别(对于我的应用程序,我可以选择一个合理的限制),问题是支持的级别越多,它会增加以指数方式加入,因此要慢得多。

      在代码中执行它肯定更容易,但对我来说这并不总是一种选择 - 有时我需要直接从 SQL 查询中获得它。


      我接受这个作为答案,因为这是我最终做的事情,它可能对其他人有用 - 但是,如果有人能想出更有效的方法,我会将其更改为他们。

      【讨论】:

        【解决方案4】:

        这里是用于从树中的任何点获取“面包屑”路径的 SQL。希望对您有所帮助。

        SELECT ancestor.id, ancestor.title, ancestor.alias 
        FROM `categories` child, `categories` ancestor 
        WHERE child.lft >= ancestor.lft AND child.lft <= ancestor.rgt 
        AND child.id = MY_CURRENT_ID 
        ORDER BY ancestor.lft
        

        凯丝

        【讨论】:

        • 这是一种获取单个 id 路径的简单方法——事实上,这是使用 MPTT 结构的巨大好处之一。但它会为每个祖先返回一行——最初的问题是批量查找所有节点的面包屑。
        【解决方案5】:

        好的。这适用于 MySQL,而不是 SQL Server 2005。它使用带有子查询的 GROUP_CONCAT。

        这应该将完整的面包屑作为单列返回。

        SELECT 
         (SELECT GROUP_CONCAT(parent.name SEPARATOR ' > ')
         FROM category parent
         WHERE node.Left >= parent.Left
         AND node.Right <= parent.Right
         ORDER BY Left
         ) as breadcrumb
        FROM category node
        ORDER BY Left
        

        【讨论】:

        • 想法没问题,但我建议将内部查询的ORDER BY Left移到GROUP_CONCAT语句中:GROUP_CONCAT(parent.name ORDER BY parent.left SEPARATOR ' &gt; ')
        【解决方案6】:

        我修改了 Kathy 的声明以获取每个元素的面包屑

        SELECT
            GROUP_CONCAT(
                ancestor.name
                ORDER BY ancestor.lft ASC
                SEPARATOR ' > '
            ),
            child.*
        FROM `categories` child
        JOIN `categories` ancestor
        ON child.lft >= ancestor.lft
        AND child.lft <= ancestor.rgt
        GROUP BY child.lft
        ORDER BY child.lft
        

        随意添加 WHERE 条件,例如

         WHERE ancestor.lft BETWEEN 6 AND 11
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多