SQL - 展平树视图

时间:2016-03-17 16:19:46

标签: sql sql-server

我在表单中使用树控件,它使用列ID,ParentID,ReasonText。 例如:

ID ParentID ReasonText
1  0        Level1A
2  0        Level1B
3  1        Level2AA
4  1        Level2AB
5  2        Level2BA
6  4        Level3ABA

我想从SQL获取结构,这样如果我选择ID = 6,例如我想返回“Level1A> Level2AB> Level3ABA”,1将返回“Level1A”。 我有一个复杂的SQL语句,可以达到4级。

DECLARE @ipid int = 45
DECLARE @pathdelimiter varchar = '>'
SELECT
    R1.ID, N'' + 
    CASE WHEN (SELECT R4.ReasonText FROM CLG_CallReasonTree R4 WHERE R4.ID = (SELECT R3.ParentID FROM CLG_CallReasonTree R3 WHERE R3.ID = (SELECT R2.ParentID FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID))) IS NOT NULL
    THEN (SELECT R4.ReasonText FROM CLG_CallReasonTree R4 WHERE R4.ID = (SELECT R3.ParentID FROM CLG_CallReasonTree R3 WHERE R3.ID = (SELECT R2.ParentID FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID))) + @pathdelimiter  ELSE '' END +
    CASE WHEN (SELECT R3.ReasonText FROM CLG_CallReasonTree R3 WHERE R3.ID = (SELECT R2.ParentID FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID)) IS NOT NULL
    THEN (SELECT R3.ReasonText FROM CLG_CallReasonTree R3 WHERE R3.ID = (SELECT R2.ParentID FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID)) + @pathdelimiter  ELSE '' END +
    CASE WHEN (SELECT R2.ReasonText FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID) IS NOT NULL
    THEN (SELECT R2.ReasonText FROM CLG_CallReasonTree R2 WHERE R2.ID = R1.ParentID) + @pathdelimiter ELSE '' END +
    R1.ReasonText AS TreePath
FROM CLG_CallReasonTree R1
WHERE R1.ID = @ipid

我确信必须有一种方法可以更简单地使用递归,但无法管理它。

2 个答案:

答案 0 :(得分:2)

declare @CLG_CallReasonTree table
(
  ID int, 
  ParentID int,
  ReasonText varchar(100)
)

insert into @CLG_CallReasonTree(ID, ParentID, ReasonText)
values
(1,  0,        'Level1A'),
(2,  0,        'Level1B'),
(3,  1,        'Level2AA'),
(4,  1,        'Level2AB'),
(5,  2,        'Level2BA'),
(6,  4,        'Level3ABA')


declare @ID int = 6

/* for single id with for_xml */
;with cteTree as
(
  select t.ID, t.ParentID, t.ReasonText, 1 as lvl
  from @CLG_CallReasonTree t
  where t.ID = @ID

  union all

  select tt.ID, tt.ParentID, tt.ReasonText, t.lvl+1
  from cteTree t
  inner join @CLG_CallReasonTree tt on tt.ID = t.ParentID
)
select stuff(
  (
    select '->' + t.ReasonText
    from cteTree t
    order by t.lvl desc
    for xml path(''), type
  ).value('.', 'varchar(max)'), 1, 2, '')


/* with reversed path - provides ability to apply like filter */
;with cteTree as
(
  select t.ID, t.ParentID, t.ReasonText, 1 as lvl, cast('/' + cast(t.ID as varchar(10)) + '/' as varchar(1000)) as tree_path
  from @CLG_CallReasonTree t
  where t.ParentID = 0

  union all

  select tt.ID, tt.ParentID, tt.ReasonText, t.lvl+1, cast('/' + cast(tt.ID as varchar(10)) + t.tree_path as varchar(1000))
  from cteTree t
  inner join @CLG_CallReasonTree tt on tt.ParentID = t.ID
)
select * 
from cteTree


/* path-like ReasonText */
declare @reftable table
(
  ID int,
  ReasonID int,
  Title varchar(100)
)

insert into @reftable(ID, ReasonID, Title)
values
(1, 3, 'AAA'),
(2, 5, 'BBB'),
(3, 0, 'CCC'),
(4, 1, 'DDD'),
(5, 6, 'FFF')

;with cteTree as
(
  select t.ID, t.ParentID, cast(t.ReasonText as varchar(1000)) as ReasonText
  from @CLG_CallReasonTree t
  where t.ParentID = 0

  union all

  select tt.ID, tt.ParentID, cast(t.ReasonText + '->' + tt.ReasonText as varchar(1000))
  from cteTree t
  inner join @CLG_CallReasonTree tt on tt.ParentID = t.ID
)
select rt.ID, rt.Title, t.ReasonText
from @reftable rt
inner join cteTree t on t.ID = rt.ReasonID

enter image description here

答案 1 :(得分:1)

您正在寻找的是下面表格的递归CTE。简单的总结是查询重复,直到它达到锚定义中设置的限制。有关其工作原理的详细信息,请参阅Recursive Queries Using Common Table Expressions

;WITH CallReasonTree (ID, ParentID, ReasonText, TreePath)
AS
(
-- Anchor member definition
    SELECT ChildReason.ID, ChildReason.ParentID, ChildReason.ReasonText,
        CONVERT(nvarchar(MAX), ChildReason.ReasonText) AS TreePath
    FROM CLG_CallReasonTree AS ChildReason
    WHERE ChildReason.ParentID = 0
    UNION ALL
-- Recursive member definition
    SELECT ChildReason.ID, ChildReason.ParentID, ChildReason.ReasonText, 
        TreePath + '>' + ChildReason.ReasonText AS TreePath
    FROM CLG_CallReasonTree AS ChildReason
    INNER JOIN CallReasonTree AS ParentReason ON ChildReason.ParentID = ParentReason.ID
)
-- Statement that executes the CTE
SELECT ID, ParentID, ReasonText, TreePath
FROM CallReasonTree
ORDER BY ID

这是结果输出:

ID  ParentID  ReasonText  TreePath
1   0         Level1A     Level1A
2   0         Level1B     Level1B
3   1         Level2AA    Level1A>Level2AA
4   1         Level2AB    Level1A>Level2AB
5   2         Level2BA    Level1B>Level2BA
6   4         Level3ABA   Level1A>Level2AB>Level3ABA