使用表变量进行中间结果的T-SQL递归?

时间:2015-07-25 11:11:37

标签: sql-server tsql recursion

我有一个棘手的逻辑问题涉及某种形式的递归,我需要帮助。

略微简化,假设有一个表格包含另一个表中要考虑的IDsAIDBID - 两个int行对同义词(每对中ID s的顺序并不重要)。我想要做的是让任何提供的ID返回(distinct)所有ID的{​​{1}}个集合,它们是ID的同义词,或者是任何同义词的同义词ID等等,最多可达到任意数量的'等级。 (需要有这样的限制来防止无限循环 - 即1-2,2-3,3-1)。例如,给定以下行:

1,2
1,3
1,4
5,1
2,6
2,7
3,8
3,9
4,10

然后对于ID = 5我希望返回1-46-10

我尝试使用表变量调用基于表的递归函数来保存中间结果并使用游标迭代表变量中的记录,但结果不完整的原因不明确(尽管我怀疑我需要找到一种方法,在向表变量添加任何行后重新初始化游标)。但我怀疑整个方法存在缺陷(这就是我没有提供现有代码的原因),如果有人有更好的建议或尝试类似的东西,我将不胜感激。

2 个答案:

答案 0 :(得分:0)

这是两种方法。第一种使用效率非常低的CTE。问题是在递归期间,您无法检查结果集中的所有其他行。虽然您可以构建已贡献给给定行的行的列表,但您无法检查是否已通过其他路径到达该行。第二种方法使用循环一次一步地填充表关系。这是一种比CTE更好的方法。

-- Sample data.
declare @RecursiveTable as Table ( AId Int, BId Int );
insert into @RecursiveTable ( AId, BId ) values
  ( 1, 2 ), ( 1, 3 ), ( 1, 4 ),
  ( 5, 1 ),
  ( 2, 6 ), ( 2, 7 ),
  ( 3, 8 ), ( 3, 9 ),
  ( 4, 10 );
select * from @RecursiveTable;

-- Walk the tree with a recursive CTE.
--   NB: This is woefully inefficient since we cannot promptly detect
--   rows that have already been processed.
declare @Start as Int = 5;
with Pairs as (
  select AId, BId, Cast( AId as VarChar(10) ) + '/' + Cast( BId as VarChar(10) ) as Pair
    from @RecursiveTable ),
 Relations as (
  select AId, BId, Cast( '|' + Pair + '|' as VarChar(1024) ) as Path
    from Pairs
    where AId = @Start or BId = @Start
  union all
  select P.AId, P.BId, Cast( R.Path + P.Pair + '|' as VarChar(1024) )
    from Relations as R inner join
      Pairs as P on P.BId = R.AId or P.AId = R.BId or
        P.BId = R.BId or P.AId = R.AId
    where CharIndex( '|' + P.Pair + '|', R.Path ) = 0
  )
  -- To see how terrible this is, try: select * from Relations
  select AId as Id
    from Relations
    where AId <> @Start
  union
  select BId
    from Relations
    where BId <> @Start
    order by Id;

-- Try again using a loop to add relations to a working table.
declare @Relations as Table ( AId Int, BId Int );
insert into @Relations
  select AId, BId
    from @RecursiveTable
    where AId = @Start or BId = @Start;
while @@RowCount > 0
  insert into @Relations
    select RT.AId, RT.BId
      from @Relations as R inner join
        @RecursiveTable as RT on RT.BId = R.BId or RT.AId = R.AId or
          RT.BId = R.AId or RT.AId = R.BId
    except
    select AId, BId
      from @Relations;
select AId as Id
  from @Relations
  where AId <> @Start
union
  select BId
  from @Relations
  where BId <> @Start
  order by Id;

答案 1 :(得分:0)

这是一个奇怪的问题,但试试这个:

CREATE FUNCTION fSynonyms(@val Int, @maxRecursions Int)
  RETURNS @ret TABLE
  (
     num    Int
  )
AS BEGIN 
  DECLARE @Values TABLE ( num Int )
  INSERT INTO @Values VALUES(@val)
  DECLARE @i Int = 0

  WHILE @i < @maxRecursions
  BEGIN
     INSERT INTO VAL
       SELECT aid 
       FROM @Values VAL
            INNER JOIN Aliases ALIAS
               ON ALIAS.bid = VAL.num

     INSERT INTO VAL
        SELECT bid
         FROM @Values VAL
              INNER JOIN Aliases ALIAS
                ON ALIAS.aid = VAL.num

     SET @i += 1
   END

   INSERT INTO @Ret
      SELECT DISTINCT num FROM @Values

    RETURN
END

您可以连接此函数的输出,该输出将包括给定值的所有已定义别名值,直到指定的递归深度。

我确信,按照这种方法,你可以摆脱光标。