寻找嵌套集的面包屑

发布于 2024-07-17 16:40:18 字数 1451 浏览 4 评论 0原文

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

例如:

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

我确实有一个用户定义的函数,它接受 userid 作为参数,并返回一个表,其中包含所有组的 id是有效的,所以只要查询中的某个地方

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

它就会起作用。


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

我需要一种能够快速运行(例如<= 1s)的方法来执行此操作。

这是在 SQL Server 2005 上。

I'm using nested sets (aka modified preorder tree traversal) to store a list of groups, and I'm trying to find a quick way to generate breadcrumbs (as a string, not a table) for ALL of the groups at once. My data is also stored using the adjacency list model (there are triggers to keep the two in sync).

So for example:

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

Which represents the tree:

  • Node A
    • Node B
      • Node C
    • Node D
      • Node E
      • Node F

I would like to be able to have a user-defined function that returns a table:

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

To make this slightly more complicated (though it's sort of out of the scope of the question), I also have user restrictions that need to be respected. So for example, if I only have access to id=3, when I run the query I should get:

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

I do have a user-defined function that takes a userid as a parameter, and returns a table with the ids of all groups that are valid, so as long as somewhere in the query

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

it will work.


I have an existing scalar function that can do this, but it just does not work on any reasonable number of groups (takes >10 seconds on 2000 groups). It takes a groupid and userid as a parameter, and returns a nvarchar. It finds the given groups parents (1 query to grab the left/right values, another to find the parents), restricts the list to the groups the user has access to (using the same WHERE clause as above, so yet another query), and then uses a cursor to go through each group and append it to a string, before finally returning that value.

I need a method to do this that will run quickly (eg. <= 1s), on the fly.

This is on SQL Server 2005.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

千と千尋 2024-07-24 16:40:18

这是帮助我从树中的任何点获取“面包屑”路径的 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

凯丝

here's the SQL that worked for me to get the "breadcrumb" path from any point in the tree. Hope it helps.

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

Kath

断念 2024-07-24 16:40:18

好的。 这是针对 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

Ok. This is for MySQL, not SQL Server 2005. It uses a GROUP_CONCAT with a subquery.

This should return the full breadcrumb as single column.

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
混浊又暗下来 2024-07-24 16:40:18

如果可以的话,请使用路径(或者我想我听说过它被称为谱系)字段,例如:

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 + '%'

If you can, use a path (or I think I've heard it referred as a lineage) field like:

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,

To get just node D and onward (psuedocode):

path = SELECT Path FROM Nodes WHERE ID = 3
SELECT * FROM Nodes WHERE Path LIKE = path + '%'
攒一口袋星星 2024-07-24 16:40:18

我修改了 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

I modified the Statement of Kathy to get breadcrumbs for every element

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

Feel free to add a WHERE condition e.g.

 WHERE ancestor.lft BETWEEN 6 AND 11
落花浅忆 2024-07-24 16:40:18

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

首先,我仅使用第一级组填充表 @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]

这是一个相当大的黑客,并且显然仅限于一定数量的级别(对于我的应用程序,我可以选择一个合理的限制),问题是支持的级别越多,它就会成倍地增加连接数量,因此速度要慢得多。

在代码中执行此操作肯定更容易,但对我来说,这并不总是一个选项 - 有时我需要直接从 SQL 查询中获得此功能。


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

What I ended up doing is making a large join that simply ties this table to itself, over and over for every level.

First I populate a table @topLevelGroups with just the 1st level groups (if you only have one root you can skip this step), and then @userGroups with the groups that user can see.

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]

This is a pretty big hack, and is obviously limited to a certain number of levels (for my app, there is a reasonable limit I can pick), with the problem that the more levels are supported, it increases the number of joins exponentially, and thus is much slower.

Doing it in code is most certainly easier, but for me that is simply not always an option - there are times when I need this available directly from a SQL query.


I'm accepting this as the answer, since it's what I ended up doing and it may work for other people -- however, if someone can come up with a more efficient method I'll change it to them.

温柔一刀 2024-07-24 16:40:18

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

SELECT * FROM table WHERE left < (currentid.left) AND 右 > (当前id.右)

no sql server specific code, but are you simply looking for :

SELECT * FROM table WHERE left < (currentid.left) AND right > (currentid.right)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文