【问题标题】:recursive query with peer relations具有对等关系的递归查询
【发布时间】:2023-03-04 14:12:01
【问题描述】:

假设有一张关系表 (entity_id, 关系, related_id)

1, A, 2     
1, A, 3      
3, B, 5 
1, C, null 
12, C, 1 
100, C, null

我需要一个能提取所有相关行的查询。 比如我查询entity_id = 1,下面的行应该被拉出来

1, A, 2     
1, A, 3      
3, B, 5 
1, C, null 
12, C, 1 

其实如果我查询entity_id = 1, 2, 3, 5, or 12,结果集应该是一样的。

这与标准的经理-员工范式不同,因为没有层次结构。关系可以朝任何方向发展。


编辑 到目前为止发布的答案都没有奏效。

我想出了一个可行的解决方案。

我会将解决方案归功于能够将这个怪物清理成更优雅的东西的人。

with tab as ( 
-- union for reversals
 select id, entity_id, r.related_id, 1 level
 , cast('/' + cast(entity_id as varchar(1000)) + '/'  as varchar(1000)) path 
  from _entity_relation r 
  where not exists(select null from _entity_relation r2 where r2.related_id=r.entity_id) 
    or r.related_id is null 
 union
 select id, related_id, r.entity_id, 1 level
 , cast('/' + cast(related_id as varchar(1000)) + '/' as varchar(1000)) path 
  from _entity_relation r 
  where not exists(select null from _entity_relation r2 where r2.related_id=r.entity_id) 
    or r.related_id is null 

-- create recursive path
union all 
 select r.id, r.entity_id, r.related_id, tab.level+1
 , cast(tab.path + '/' + cast(r.entity_id as varchar(100)) + '/' + '/' + cast(r.related_id as varchar(1000)) + '/' as varchar(1000)) path 
  from _entity_relation r 
  join tab 
  on tab.related_id = r.entity_id   
) 

select x.id
    , x.entity_id
    ,pr.description as relation_description
    ,pt.first_name + coalesce(' ' + pt.middle_name,'') + ' ' + pt.last_name as relation_name
    ,CONVERT(CHAR(10), pt.birth_date, 101) as relation_birth_date   
from (

select entity_id, MAX(id) as id from (
select distinct tab.id, entity_id
from tab 
join( 
    select path 
    from tab  
    where entity_id=@in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, related_id
from tab 
join( 
    select path 
    from tab  
    where entity_id=@in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, entity_id
from tab 
join( 
    select path 
    from tab  
    where related_id=@in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
union
select distinct tab.id, related_id
from tab 
join( 
    select path 
    from tab  
    where related_id=@in_entity_id
) p on p.path like tab.path + '%' or tab.path like p.path + '%'
) y
group by entity_id
) x
join _entity_relation pr on pr.id = x.id
join _entity pt on pt.id = x.entity_id
where x.entity_id <> @in_entity_id;

【问题讨论】:

  • 如果您要查询entity_id = 1,您的所有记录不应该都以1 开头,所以应该只返回第一条、第二条和第四条记录吗? ..好的,我明白了...您希望递归然后通过reated_id 并包括这些行...但是您如何返回12,C,1?递归无法到达那里,因为它已经转过来了......
  • 这是一种对等关系——这种关系可以朝任何一个方向发展
  • 可以存在循环关系吗?是否允许添加5, D, 1建立3 - 5 - 1 - 3 - 5 等的关系路径?
  • 是的 - 这就是让它变得棘手的事情......

标签: sql sql-server recursive-query


【解决方案1】:

请注意您的数据,因为要完成您的任务,您必须避免循环引用。以下查询可以优化,但肯定会起作用

;with tab as (
 select entity_id, relationship, related_id, 1 level, cast('/' + cast(entity_id as varchar(1000)) as varchar(1000)) path
  from #r r
  where not exists(select null from #r r2 where r2.related_id=r.entity_id)
    or r.related_id is null
union all
 select r.entity_id, r.relationship, r.related_id, tab.level+1, cast(tab.path + '/' + cast(r.entity_id as varchar(100)) as varchar(1000)) path
  from #r r
  join tab
  on tab.related_id = r.entity_id  
)
select distinct tab.* 
    from tab
    join(
select path
    from tab 
    where entity_id=1) p
    on p.path like tab.path + '%' or tab.path like p.path + '%'

【讨论】:

  • 查询结果在最终结果中包含不需要的记录100,c,null
  • 我已经测试了查询。结果不包括不需要的记录并且运行良好。
【解决方案2】:

使用两个 CTE 的解决方案

我首先创建了一个具有双向关系的表,然后创建了一个递归 CTE,它使用这两种方式的结果来构建具有祖先路径的整个层次结构...

with both as
(
    select *, 0 as rev
    from t
    where related_id is not null

    union

    select *, 1
    from t
),
recurs as
(
    select *, cast('/' as varchar(100)) as anc
    from both
    where entity_id is null

    union all

    select b.*, cast(re.anc + cast(b.entity_id as varchar) + '/' as varchar(100))
    from both b
        join recurs re
        on (re.related_id = b.entity_id)
    where charindex('/'+cast(isnull(b.entity_id,'') as varchar)+'/', re.anc) = 0
)
select *
/*
    THIS ONE SHOULD BE USED TO RETURN TO ORIGINAL
    case when is_reverse = 1 then related_id else entity_id end as entity_id,
    relationship,
    case when is_reverse = 0 then related_id else entity_id end as related_id
*/
from recurs
where related_id = xXx or
      charindex('/'+cast(xXx as varchar)+'/', anc) != 0

用实际值替换xXx

这个查询假定根元素是带有entity_id = null 的那个,所以它从那里构建整个递归。如果不是这种情况,您将不得不相应地进行更改。

我添加了循环检查,循环是 1,2,3,4,5,1 还是 1,2,3,4,5,3。 ..所以全部或部分循环。两者都可以。

【讨论】:

  • @mson:我对查询进行了大量编辑。我用您的数据创建了一个表并对其进行了测试。递归避免了关系循环,它似乎按预期工作。
  • 非常感谢您的努力 - 我现在正在测试它
  • @mson:您能否更具体地说明什么不起作用?因为首先基于您的数据,结果很好,其次,我根据对问题的理解创建了此查询,可能与您的不同。但我相信您可以使用我的查询作为解决问题的起点。
猜你喜欢
  • 2021-12-18
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 2019-09-12
  • 1970-01-01
  • 2015-05-05
相关资源
最近更新 更多