【问题标题】:T-SQL Execute Recursion for Each Value in Multiple ColumnsT-SQL 对多列中的每个值执行递归
【发布时间】:2017-12-12 05:52:05
【问题描述】:

我有一个包含两列的数据集。第一列中的人可能在第二列中的人的控制范围内(即每个人都在迈克尔的控制范围内,德怀特和斯坦利在吉姆的控制范围内):

source_id     source          target_id     target        
1             Michael Scott   5             Kelly Kapoor  
3             Dwight Schrute  2             Jim Halpert
4             Stanley Hudson  2             Jim Halpert
2             Jim Halpert     5             Kelly Kapoor

我有一张表格,列出了每个人和他们的主管:

person_id       person           supervisor_id     supervisor
1               Michael Scott    0                 None
2               Jim Halpert      1                 Michael Scott
3               Dwight Schrute   2                 Jim Halpert
4               Stanley Hudson   2                 Jim Halpert
6               Ryan Howard      1                 Michael Scott
5               Kelly Kapoor     6                 Ryan Howard

我有一段代码使用递归从上表中查找单个人的控制范围:

with anchor as
(
    select person_id, supervisor_id from table where unique_id = @ID
    union all
    select a.person_id, a.supervisor_id 
    from table a
    inner join Anchor b ON b.person_id = a.supervisor_id
)

select a.person_id
from anchor a

这段代码可以变成存储过程或函数。为 Jim 运行它(例如),返回:

person_id
3 (Dwight Schrute)
4 (Stanley Hudson)

如何将初始数据集中的每个值(来自源列和目标列)与前面代码块返回的列中的值进行比较?换句话说,对于source 中的每一行,我需要检查该名称是否在target 的控制范围内。此外,对于target 中的每一行,我需要检查该名称是否在source 的控制范围内。

期望的最终结果:

source_id     source          target_id     target         isEitherPersonInOther'sSOC
1             Michael Scott   5             Kelly Kapoor   Yes
3             Dwight Schrute  2             Jim Halpert    Yes
4             Stanley Hudson  2             Jim Halpert    Yes
2             Jim Halpert     5             Kelly Kapoor   No

我知道迭代很糟糕(即使用游标或 while 循环为每一行运行存储过程)。我也知道cross apply 和一个函数一起可能会起作用,但我一直无法解决这个问题。

感谢大家提供的任何见解!

【问题讨论】:

  • 首先,我会给每个人一个唯一的 ID,这样你就不会'加入名字......如果你有两个 Jim Halperts 怎么办
  • 我有一个唯一的 ID,只是为了便于说明而使用了名称。
  • 永远不要忽略可能相关的内容;)因为解决方案不会假设
  • 谢谢 - 我更新了它以包含 ID
  • 确实如此,因为它们是独立的数据集。源和目标是人员之间链接的数据集 - 可以链接两个主管,可以链接两个没有控制范围的人,等等。或者,人员和主管表是斯克兰顿 Dunder Mifflin 的员工主表。

标签: sql sql-server tsql recursion stored-procedures


【解决方案1】:

看起来您需要的是将递归 CTE 和输出 ID 对放入临时表中。我正在想象这样的事情:

DECLARE @id int
DECLARE cx CURSOR FOR 
    SELECT person_id FROM PersonSupervisor

CREATE TABLE #tmpPS (Supervisor int, personInChain int)

OPEN cx
FETCH NEXT FROM cx
    INTO @id

WHILE @@FETCH_STATUS = 0
BEGIN
    with anchor as
    (
        select person_id, supervisor_id from table where unique_id = @ID
        union all
        select a.person_id, a.supervisor_id 
        from table a
        inner join Anchor b ON b.person_id = a.supervisor_id
    )
    INSERT INTO #tmpPS
    select @id, a.person_id
    from anchor a

    FETCH NEXT FROM cx
        INTO @id
END
Close cx
Deallocate cx

这将创建一个包含所有关系的表,并以递归方式展开。然后,您可以使用此子查询输出任何给定的人是否高于或低于给定的其他人。将其添加到输出基本网格的任何查询中:

SELECT
    SourceId,
    Source,
    TargetId,
    Target,
    CASE
        WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = SourceId and PersonInChain = TargetId)
            THEN 'Yes'
        WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = TargetId and PersonInChain = SourceId)
            THEN 'Yes'
        ELSE 'No'
    END as [isEitherPersonInOther'sSOC]
FROM ....

这也意味着您可以在其中分离关系的版本 - 如果第一个查询,TargetId 是 SourceId 的下属。如果是第二个查询,则 TargetId 优于 SourceId。

【讨论】:

    【解决方案2】:

    必须有更好的方法来做到这一点,但这绝对是一种方法——注意代码会创建永久表以在函数中使用:

        create table dbo.[source]
    (
        source_id  int,
        [source] nvarchar(500),
        target_id int,
        [target] nvarchar(500)
    )
    
    insert into [source]
    select 1, 'Michael Scott', 5, 'Kelly Kapoor'
    union all select 3,'Dwight Schrute',2,'Jim Halpert'
    union all select 4 ,'Stanley Hudson',2,'Jim Halpert'
    union all select 2  ,'Jim Halpert',5,'Kelly Kapoor'
    
    create table dbo.supervisors
    (
        person_id int,
        person nvarchar(500),
        supervisor_id int,
        supervisor nvarchar(500)
    )
    
    insert into dbo.supervisors
    select 1,'Michael Scott', 0,'None'
    union all select 2,'Jim Halpert',1,'Michael Scott'
    union all select 3,'Dwight Schrute',2,'Jim Halpert'
    union all select 4,'Stanley Hudson',2,'Jim Halpert'
    union all select 6,'Ryan Howard',1,'Michael Scott'
    union all select 5 ,'Kelly Kapoor',6,'Ryan Howard'
    
    
    go
    
    
    create function dbo.fn_isinspanofcontrol
    (
        @sourceid int,
        @targetid int
    )
    RETURNS varchar(1)
    
    as
    begin
    
    declare @retVal varchar(1)
    declare @tbl table
    (
        person_id int
    )
    
    ;with anchor as
                (
                    select person_id, supervisor_id from supervisors where person_id = @sourceid
                    union all
                    select a.person_id, a.supervisor_id 
                    from supervisors a
                    inner join Anchor b ON b.person_id = a.supervisor_id
                )
    insert into @tbl
    select a.person_id
    from anchor a
    where   
        a.person_id = @targetid
    
    ;with anchor as
                (
                    select person_id, supervisor_id from supervisors where person_id = @targetid
                    union all
                    select a.person_id, a.supervisor_id 
                    from supervisors a
                    inner join Anchor b ON b.person_id = a.supervisor_id
                )
    insert into @tbl
    select a.person_id
    from anchor a
    where   
        a.person_id = @sourceid
    
    
    
    if exists( select 1
                from @tbl 
            )
    begin
        set @retVal = 'Y'
    end
    else
    begin
        set @retVal = 'N'
    end
    
        return @retVal
    
    end
    
    
    select 
        *, dbo.fn_isinspanofcontrol(source_id,target_id)
    from [source]
    

    【讨论】:

      猜你喜欢
      • 2011-11-09
      • 1970-01-01
      • 2013-11-03
      • 2012-03-16
      • 2020-03-28
      • 2020-02-15
      • 1970-01-01
      • 1970-01-01
      • 2022-01-18
      相关资源
      最近更新 更多