SQL:遍历带圆圈的图

时间:2018-06-27 15:20:41

标签: sql sql-server recursion graph

我有一个带有属性的对象表。一些对象共享相同的属性,在这种情况下,我认为它们是通过它们的公共属性连接的:

object   attribute
  1          a
  1          b
  2          a
  2          c
  3          b
  3          d
  4          c
  5          d
  6          e
  7          e

将其视为网络:

5 - d - 3 - b - 1 - a - 2 - c - 4    7 - e - 6

在此示例中,网络只是一条线。它可以是一棵树,有圆圈等。我需要找到通过通用属性和其他对象与对象1连接的所有对象的集合。在这种情况下,它就是集合(1, 2, 3, 4, 5)

我尝试使用递归:

with graph as (select object, attribute
               from   my_table where object = 1
               union all
               select my_table.object, my_table.attribute
               from   graph join my_table on my_table.attribute = graph.attribute
                                             and my_table.object <> graph.object
              )
select * from graph

它适用于非圆形图形,但是当我有圆形时,我会继续在graph表中添加相同的元素。如何跟踪已添加的元素?

编辑:它实际上不适用于任何图形,因为到达元素3后,它返回到元素1并在它们之间反弹。

更新:答案对我没有用,因为它一次又一次地通过相同的节点(对象之间存在循环关系),但这使我走上了正轨!在与SQL长期斗争之后,不允许在CTE中做任何事情,我发现这对于我的图形来说足够快地执行:

drop table if exists #my_table
create table #my_table 
        (obj int
        ,attribute varchar(10)
        )

insert into #my_table values
(1, 'a'),
(1, 'b'),
(2, 'a'), 
(2, 'c'),
(3, 'b'),
(3, 'd'), 
(4, 'c'),
(5, 'd'),
(6, 'e'),
(7, 'e')


;with grouped as (select 1 step
                        -- this will be the full history of all objects and attributes that are connected to the initial object
                        ,convert(varchar(max), stuff((select    ', ' + convert(varchar(10), dc.obj) + ' - ' + dc.attribute 
                                                    FROM    #my_table dc
                                                    where   obj = 1
                                                    for xml path ('')), 1, 2, '')
                                ) history
                union ALL
                SELECT  step + 1 as step
                        ,convert(varchar(max), grouped.history + (SELECT    ', ' + convert(varchar(10), dc2.obj) + ' - ' + dc2.attribute
                                                                from    #my_table dc
                                                                JOIN    #my_table dc2 on dc2.obj = dc.obj 
                                                                        -- the new object should be connected to an attribute in our history
                                                                WHERE   grouped.history like '%' + convert(varchar(10), dc.attribute) + '%'
                                                                        -- but this should be a new object (not in the history yet)
                                                                        and grouped.history not like '%' + convert(varchar(10), dc2.obj) + ' - ' + dc.attribute + '%'
                                                                for xml path (''))
                                ) history 
                FROM    grouped
                where   grouped.history is not null
                )
select  * from grouped

1 个答案:

答案 0 :(得分:1)

有必要存储有关经过的节点的信息。

declare @my table(obj int, attr char(1));

insert into @my(obj,attr) values
  (1,'a'),
  (1,'b'),
  (2,'a'),
  (2,'c'),
  (3,'b'),
  (3,'d'),
  (4,'c'),
  (5,'d'),
  (6,'e'),
  (7,'e');

with
  t as(
    select 1 lv,obj,attr,row_number() over(order by(select 0)) gr,
      cast('/'+str(obj,5)+right(space(4)+attr,5)+'/' as varchar(max)) hy
    from @my
    where obj=5
    union all
    select t.lv+1,my.obj,my.attr,t.gr,
      t.hy+str(my.obj,5)+right(space(4)+my.attr,5)+'/'
    from t join @my my on t.obj=my.obj or t.attr=my.attr
    where t.hy not like '%/'+str(my.obj,5)+right(space(4)+my.attr,5)+'/%'
  ),
  p as(
    select *,row_number() over(partition by gr order by lv desc) tp
    from t
  )
select * from p where tp=1
option(maxrecursion 0);