2

我在构建返回 XML 样式层次结构的查询时遇到了极大的困难。

我们有一个数据库表,其中包含我们网站的 URL 层次结构。该表包含以下列:ID、URL、DisplayName、ParentID、ItemOrder

父 ID 在当前项与其父项之间形成递归关系。该项目应位于其在层次结构中的父级之下,并且还应使用项目顺序针对层次结构中同一级别的项目进行排序。

我设法使递归查询正常工作,因此它按顺序向下钻取层次结构,但我也无法按项目顺序对其进行排序。

我当前的查询如下:

WITH Parents AS
(
SELECT MenuItemId, URL, ParentItemId, ItemOrder
FROM CambsMenu

UNION ALL

SELECT si.MenuItemId, si.URL, si.ParentItemId, si.ItemOrder
FROM CambsMenu si INNER JOIN Parents p
ON si.ParentItemId = p.MenuItemId
)

SELECT DISTINCT *
FROM Parents
4

6 回答 6

11

正常的分层方法:

select *
into emp
from 
(values
(1, 'President', NULL),
(2, 'Vice President', 1),
(3, 'CEO', 2),
(4, 'CTO', 2),
(5, 'Group Project Manager', 4),
(6, 'Project Manager 1', 5),
(7, 'Project Manager 2', 5),
(8, 'Team Leader 1', 6),
(9, 'Software Engineer 1', 8),
(10, 'Software Engineer 2', 8),
(11, 'Test Lead 1', 6),
(12, 'Tester 1', 11),
(13, 'Tester 2', 11),
(14, 'Team Leader 2', 7),
(15, 'Software Engineer 3', 14),
(16, 'Software Engineer 4', 14),
(17, 'Test Lead 2', 7),
(18, 'Tester 3', 17),
(19, 'Tester 4', 17),
(20, 'Tester 5', 17)
) as x(emp_id, emp_name, mgr_id)

询问:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as
(
 select  
  a.emp_id, a.emp_name, 0, a.mgr_id,  
  a.emp_name
 from emp a
 where a.mgr_id is null

 union all

 select 
  b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

  sort || ' : ' || b.emp_name 

 from emp b
 join org on org.emp_id = b.mgr_id
)
select 
 emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name, sort 
from org
order by sort

输出:

 emp_id |            emp_name             |                                                        sort                                                        
--------+---------------------------------+--------------------------------------------------------------------------------------------------------------------
      1 | President                       | President
      2 |   Vice President                | President : Vice President
      3 |     CEO                         | President : Vice President : CEO
      4 |     CTO                         | President : Vice President : CTO
      5 |       Group Project Manager     | President : Vice President : CTO : Group Project Manager
      6 |         Project Manager 1       | President : Vice President : CTO : Group Project Manager : Project Manager 1
      8 |           Team Leader 1         | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1
      9 |             Software Engineer 1 | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1 : Software Engineer 1
     10 |             Software Engineer 2 | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Team Leader 1 : Software Engineer 2
     11 |           Test Lead 1           | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1
     12 |             Tester 1            | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1 : Tester 1
     13 |             Tester 2            | President : Vice President : CTO : Group Project Manager : Project Manager 1 : Test Lead 1 : Tester 2
      7 |         Project Manager 2       | President : Vice President : CTO : Group Project Manager : Project Manager 2
     14 |           Team Leader 2         | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2
     15 |             Software Engineer 3 | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2 : Software Engineer 3
     16 |             Software Engineer 4 | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Team Leader 2 : Software Engineer 4
     17 |           Test Lead 2           | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2
     18 |             Tester 3            | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 3
     19 |             Tester 4            | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 4
     20 |             Tester 5            | President : Vice President : CTO : Group Project Manager : Project Manager 2 : Test Lead 2 : Tester 5
(20 rows)

现在让我们覆盖对 Group Project Managers 的排序,让项目经理 2 在项目经理 1 之前,项目经理 1 在项目经理 2 之后。让我们也让测试人员 4 在测试人员 3 之前,测试人员 3 在测试人员 4 之后

alter table emp add column order_override int null;

update emp set order_override = 1 where emp_id = 7; -- PM 2
update emp set order_override = 2 where emp_id = 6; -- PM 1

update emp set order_override = 1 where emp_id = 19; -- Tester 4
update emp set order_override = 2 where emp_id = 18; -- Tester 3

询问:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as
(
 select  
  a.emp_id, a.emp_name, 0, a.mgr_id,  
  a.emp_name
 from emp a
 where a.mgr_id is null

 union all

 select 
  b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

  sort || ' : ' || coalesce( lpad(order_override::text, 10, '0'), b.emp_name )
 from emp b
 join org on org.emp_id = b.mgr_id
)
select 
 emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name, sort 
from org
order by sort

输出:

 emp_id |            emp_name             |                                                    sort                                                     
--------+---------------------------------+-------------------------------------------------------------------------------------------------------------
      1 | President                       | President
      2 |   Vice President                | President : Vice President
      3 |     CEO                         | President : Vice President : CEO
      4 |     CTO                         | President : Vice President : CTO
      5 |       Group Project Manager     | President : Vice President : CTO : Group Project Manager
      7 |         Project Manager 2       | President : Vice President : CTO : Group Project Manager : 0000000001
     14 |           Team Leader 2         | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2
     15 |             Software Engineer 3 | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2 : Software Engineer 3
     16 |             Software Engineer 4 | President : Vice President : CTO : Group Project Manager : 0000000001 : Team Leader 2 : Software Engineer 4
     17 |           Test Lead 2           | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2
     19 |             Tester 4            | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : 0000000001
     18 |             Tester 3            | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : 0000000002
     20 |             Tester 5            | President : Vice President : CTO : Group Project Manager : 0000000001 : Test Lead 2 : Tester 5
      6 |         Project Manager 1       | President : Vice President : CTO : Group Project Manager : 0000000002
      8 |           Team Leader 1         | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1
      9 |             Software Engineer 1 | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1 : Software Engineer 1
     10 |             Software Engineer 2 | President : Vice President : CTO : Group Project Manager : 0000000002 : Team Leader 1 : Software Engineer 2
     11 |           Test Lead 1           | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1
     12 |             Tester 1            | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1 : Tester 1
     13 |             Tester 2            | President : Vice President : CTO : Group Project Manager : 0000000002 : Test Lead 1 : Tester 2
(20 rows)

在数据投影中没有排序列:

with recursive org(emp_id, emp_name, emp_level, mgr_id, sort) as
(
 select  
  a.emp_id, a.emp_name, 0, a.mgr_id,  
  a.emp_name
 from emp a
 where a.mgr_id is null

 union all

 select 
  b.emp_id, b.emp_name, emp_level + 1, b.mgr_id, 

  sort || ' : ' || coalesce( lpad(order_override::text, 10, '0'), b.emp_name )
 from emp b
 join org on org.emp_id = b.mgr_id
)
select 
 emp_id, repeat(' ', emp_level * 2) || emp_name as emp_name
from org
order by sort

输出:

 emp_id |            emp_name             
--------+---------------------------------
      1 | President
      2 |   Vice President
      3 |     CEO
      4 |     CTO
      5 |       Group Project Manager
      7 |         Project Manager 2
     14 |           Team Leader 2
     15 |             Software Engineer 3
     16 |             Software Engineer 4
     17 |           Test Lead 2
     19 |             Tester 4
     18 |             Tester 3
     20 |             Tester 5
      6 |         Project Manager 1
      8 |           Team Leader 1
      9 |             Software Engineer 1
     10 |             Software Engineer 2
     11 |           Test Lead 1
     12 |             Tester 1
     13 |             Tester 2
(20 rows)

项目经理 2 在项目经理 1 之前。测试人员 4 在测试人员 3 之前

如果存在 order_override(non-null),则该技术在于 b.name 的数字文本替换:

sort || ' : ' || coalesce( lpad(order_override::text, 10, '0'), b.emp_name )

上面的代码是Postgres,要转换成Sql Server,去掉单词,RECURSIVE改成REPEAT,到。REPLICATE||+

相当于...

lpad(order_override::text, 10, '0')

...是:

RIGHT( REPLICATE('0',10) + CONVERT(VARCHAR, order_override), 10)
于 2010-05-14T15:37:07.257 回答
6

感谢您的所有回复!

为了完整起见,这是我想出的最终解决方案。它创建一个由点分隔为子部分的字符串。下面的解决方案仅支持根节点中最多 9999 个项目,但您可以通过简单地更改 STR(ItemOrder,4) 命令中的数字来增加前导零的数量来轻松扩展它

WITH Parents AS
(
SELECT MenuItemId,
    URL,
    ParentItemId,
    DisplayName,
    OpenInNewWindow,
    ItemOrder,
    CAST((REPLACE(STR(ItemOrder,4),' ','0')) AS nvarchar(max)) AS OrderString
FROM CambsMenu
WHERE ParentItemId IS NULL

UNION ALL

SELECT si.MenuItemId,
    si.URL,
    si.ParentItemId,
    si.DisplayName,
    si.OpenInNewWindow,
    si.ItemOrder,
    (p.OrderString + '.' + CAST((REPLACE(STR(si.ItemOrder,4),' ','0')) AS nvarchar(max))) AS OrderString
FROM CambsMenu si INNER JOIN Parents p
ON si.ParentItemId = p.MenuItemId
)

SELECT * FROM Parents ORDER BY OrderString ASC
于 2010-05-14T15:36:06.843 回答
3

兄弟姐妹的数量是一个已知值吗?级别数是否已知?如果是这样,您可以对 ItemOrder 执行操作,以保证每个项目都有一个唯一的 ItemOrder,然后仅按该值排序。

例如,假设任何项的子项不能超过 10 个(ItemOrder 的范围从 0 到 9),并且最多有 5 个级别。我现在要做的是使第一个父 ItemOrder 成为当前项目订单的 10000 倍,而它的子 ItemOrder 将是当前 ItemOrder 加上它的父 ItemOrder 的 1000 倍,依此类推,每次删除一个 0你下降一个层次。

WITH Parents AS
(
SELECT MenuItemId,
    URL,
    ParentItemId,
    (ItemOrder * 10000) AS ItemOrder,
    10000 AS Multiplier
FROM CambsMenu
WHERE ParentItemId IS NULL

UNION ALL

SELECT si.MenuItemId,
    si.URL,
    si.ParentItemId,
    (p.ItemOrder + si.ItemOrder * p.Multiplier/ 10) as ItemOrder,
    (p.Multiplier / 10) as Multiplier
FROM CambsMenu si INNER JOIN Parents p
ON si.ParentItemId = p.MenuItemId
)

SELECT * FROM Parents ORDER BY ItemOrder

如果级别或子级的数量未知,您可以使用类似的方法,但您可以构建一个字符串 ItemOrder,而不是构建一个数字 ItemOrder,保证字符串 '1.10.20' 低于字符串 '2.1'

于 2010-05-14T13:29:20.217 回答
1
WITH Parents AS
(
SELECT MenuItemId, URL, ParentItemId, ItemOrder, 0 AS Level, Cast((ItemOrder+1000) as Varchar(MAX)) as MatPath
FROM CambsMenu
WHERE ParentItemId IS NULL

UNION ALL

SELECT si.MenuItemId, si.URL, si.ParentItemId, si.ItemOrder, Level + 1, MathPath + '.' CAST((si.ItemOrder+1000) as Varchar(MAX)
FROM CambsMenu si INNER JOIN Parents p
ON si.ParentItemId = p.MenuItemId
)

SELECT DISTINCT *
FROM Parents
ORDER BY MatPath

编辑:答案已更新,最初是按级别排序,没有被要求。此外,答案未经测试。再次更新,种子查询未过滤 IS NULL

EDIT2:这是一个更新,它将使用浮点数和子查询来获得最大数量的叶子/分支;假设 ItemOrder 从 1 开始递增,没有漏洞,并且为每个父项重新启动。这可以转换回使用整数,因为这样会更明显的是排序如何溢出/松散精度与级别数。

WITH Hierarchy AS
(
  SELECT MenuItemID, 
         URL, 
         ParentItemId, 
         ItemOrder,
         0 as level, 
         cast(1 as float) as hord
  FROM CambsMenu
  WHERE ParentItemId IS NULL 
UNION ALL
  SELECT r.MenuItemId, 
         r.URL, 
         r.PrentItemId,
         r.ItemOrder, 
         h.level + 1, 
         h.hord + r.ItemOrder/power(
           (SELECT MAX(n)+1 
            FROM (SELECT count(*) AS n 
                  FROM CambsMenu 
                  GROUP BY ParentItemId) t), h.level+1)
  FROM CambsMenu r INNER JOIN Hierarchy h
  ON r.ParentItemId = h.MenuItemId
)
SELECT *
FROM Hierarchy
ORDER BY hord;
于 2010-05-14T12:56:03.403 回答
1

尽管这是一篇旧帖子,但我还没有看到这个答案,而且它似乎没有其他一些答案所具有的缺点。我建议使用 RANK() 函数来正确排序递归结果集。这种方法对更狂野的数据更加宽容。此解决方案假定在您的递归中,任何一个结果下方的子结果不超过 99 个,但如果您有数千、数百万甚至更多,则可以轻松扩展它。修改它以使用您的数据集。

WITH    Forms
          AS (
              SELECT    FormId,
                        CAST(Caption AS VARCHAR(MAX)) AS Caption,
                        1 AS Depth,
                        CAST('01' AS VARCHAR(MAX)) AS [Rank]
              FROM      fx_NavTree
              WHERE     ParentFormId IS NULL
              UNION ALL
              SELECT    nt.FormId,
                        CAST(SPACE(f.Depth * 2) + nt.Caption AS VARCHAR(MAX)) AS Caption,
                        f.Depth + 1 AS Depth,
                        f.[Rank] + '-' + RIGHT('00' + CAST(RANK() OVER (PARTITION BY f.[Rank] ORDER BY nt.SortOrder) AS VARCHAR(MAX)), 2) AS [Rank]
              FROM      fx_NavTree AS nt
              INNER JOIN Forms AS f ON nt.ParentFormId = f.FormId
             )
    SELECT  *
    FROM    Forms
    ORDER BY Forms.[Rank];

在 Ben 的例子中,他会尝试 RANK() ItemOrder 列。他的解决方案应该是这样的:

WITH Parents AS
(
SELECT MenuItemId,
       CAST(URL as VARCHAR(MAX)) as URL,
       ParentItemId,
       CAST(ItemOrder AS VARCHAR(MAX)) as ItemOrder
FROM CambsMenu

UNION ALL

SELECT si.MenuItemId,
       CAST(si.URL AS VARCHAR(MAX)) as URL,
       si.ParentItemId,
       p.ItemOrder + '-' + RIGHT('00' + CAST(RANK() OVER (PARTITION BY 
p.ItemOrder ORDER BY si.ItemOrder) AS VARCHAR(MAX)), 2) AS ItemOrder
FROM CambsMenu si INNER JOIN Parents p
ON si.ParentItemId = p.MenuItemId
)

SELECT DISTINCT *
FROM Parents
于 2017-08-17T13:01:34.683 回答
0

SQL 不支持“层次结构”或“树”或“图形”类型,因为 SQL/关系模型本质上是为了使这些类型过时(需要)而发明的。

您编写了一个查询,计算数学术语中称为“传递闭包”的内容。我怀疑这真的是你想要的。如果关系(“表”)具有对 (1 2) 和 (2 3),那么您的查询将包括结果对 (1 3)。但是,(在此示例中)我怀疑您不希望 XML 样式的结果包含一个将数字 3 作为数字 1 的直接子级的标记...

我怀疑你想要的更有可能通过使用关系代数的 GROUP 运算符来实现。警告:这与“GROUP BY”并不是一回事(关系代数的 GROUP 运算符生成的表包含的列的值本身就是一个表 - 例如一个包含某个父级的所有直接子级的表),它是很可能您的特定 DBMS 不支持它,在这种情况下,您几乎会“被您的 DBMS 抛弃”并且“除了编写所有该死的狗屎之外别无选择(我指的是递归)你自己”。

于 2010-05-14T14:52:07.937 回答