-
03-07-2019 - |
题
我正在使用嵌套集(又名修改的前序树遍历)来存储组列表,并且我正在尝试找到一种快速方法来一次为所有组生成面包屑(作为字符串,而不是表)。我的数据也使用邻接列表模型存储(有触发器使两者保持同步)。
例如:
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
- 节点B
我希望能够有一个返回表的用户定义函数:
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 上。
解决方案 4
我最终做的是创建一个大型连接,简单地将这个表与其自身联系起来,对于每个级别一遍又一遍。
首先,我仅使用第一级组填充表 @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 查询中获得此功能。
我接受这个作为答案,因为这是我最终所做的,并且可能对其他人有用——但是,如果有人能想出更有效的方法,我会将其更改为他们。
其他提示
这是帮助我从树中的任何点获取“面包屑”路径的 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
凯丝
好的。这是针对 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
如果可以的话,使用路径(或者我想我听说过它被称为谱系)字段,例如:
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 + '%'
我修改了凯西的声明以获得每个元素的面包屑
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
没有 sql server 特定代码,但您只是在寻找:
SELECT * FROM 表 WHERE 左 < (currentid.left) AND 右 > (currentid.right)