在SQL中实现递归查询

时间:2016-01-22 01:31:21

标签: sql sql-server recursive-query

我有一个关于使用递归SQL的问题,其中我有以下表格结构

产品可以分为多组(为了清楚起见,我不使用int)

-i

现在问题我想了解所有相关产品 所以,如果我通过产品1 ,那么我需要以下结果

CREATE TABLE ProductGroups(ProductName nvarchar(50), GroupName nvarchar(50))

INSERT INTO ProductGroups(ProductName, GroupName) values 
('Product 1', 'Group 1'),
('Product 1', 'Group 2'),
('Product 2', 'Group 1'),
('Product 2', 'Group 6'),
('Product 3', 'Group 7'),
('Product 3', 'Group 8'),
('Product 4', 'Group 6')


+-----------+---------+
|  Product  |  Group  |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |
| Product 3 | Group 7 |
| Product 3 | Group 8 |
| Product 4 | Group 6 | 
+-----------+---------+

所以基本上我想首先找出产品1的所有组,然后为每个组找出所有产品等等......

  1. 产品1 =>第1组,第2组;
  2. 第1组=>产品1,产品2(第1组和产品1已经存在,因此应该避免,否则将进入无限 环);
  3. 第2组=>产品1(已经存在与上述相同);
  4. 产品2 =>第1组,第6组(第1组和第2组已存在)
  5. 第6组=>产品4

4 个答案:

答案 0 :(得分:4)

可以使用递归查询来完成,但它不是最佳的,因为SQL Server不允许您将递归表作为集引用。因此,您最终必须保留路径字符串以避免无限循环。如果使用整数,则可以使用hierarchyid替换路径字符串。

with r as (
    select ProductName Root, ProductName, GroupName, convert(varchar(max), '/') Path from ProductGroups
    union all
    select r.Root, pg.ProductName, pg.GroupName, convert(varchar(max), r.Path + r.ProductName + ':' + r.GroupName + '/')
    from r join ProductGroups pg on pg.GroupName=r.GroupName or pg.ProductName=r.ProductName
    where r.Path not like '%' + pg.ProductName + ':' + pg.GroupName + '%'
)

select distinct ProductName, GroupName from r where Root='Product 1'

http://sqlfiddle.com/#!3/a65d1/5/0

答案 1 :(得分:3)

我不认为使用递归CTE可以实现这一点,因为每个递归定义只允许一个递归引用。

我确实设法使用while循环来实现它,这可能不如cte:

declare @related table (ProductName nvarchar(50), GroupName nvarchar(50))

-- base case
insert @related select * from ProductGroups where ProductName='Product 1'

-- recursive step
while 1=1
begin

    -- select * from @related -- uncomment to see progress

    insert @related select p.*
    from @related r
    join ProductGroups p on p.GroupName=r.GroupName or p.ProductName=r.ProductName
    left join @related rr on rr.ProductName=p.ProductName and rr.GroupName=p.GroupName
    where rr.ProductName is null        

    if @@ROWCOUNT = 0
        break;

end

select * from @related

在部署之前,你一定要小心上面的实际尺寸数据基准!

答案 2 :(得分:2)

这并不容易。您正在建模equivalence classes。 SQL是一个设置语言,您正在查看一个类 - 一组集:

https://www.simple-talk.com/sql/t-sql-programming/the-sql-of-membership-equivalence-classes--cliques/

答案 3 :(得分:2)

你可以这样做。

DECLARE @ProductGroups AS TABLE (
         ProductName NVARCHAR(50) ,
         GroupName NVARCHAR(50)
        )

INSERT  INTO @ProductGroups
        ( ProductName, GroupName )
VALUES  ( 'Product 1', 'Group 1' ),
        ( 'Product 1', 'Group 2' ),
        ( 'Product 2', 'Group 1' ),
        ( 'Product 2', 'Group 6' ),
        ( 'Product 3', 'Group 7' ),
        ( 'Product 3', 'Group 8' ),
        ( 'Product 4', 'Group 6' );
;
WITH    cte
          AS ( SELECT   a.ProductName
               FROM     @ProductGroups a
               WHERE    a.GroupName IN ( SELECT x.GroupName
                                         FROM   @ProductGroups x
                                         WHERE  x.ProductName = 'Product 1' )
             ),
        cte2
          AS ( SELECT   GroupName
               FROM     @ProductGroups
               WHERE    ProductName IN ( SELECT x.ProductName
                                         FROM   cte x )
             )
     SELECT *
     FROM   @ProductGroups
     WHERE  GroupName IN ( SELECT   x.GroupName
                           FROM     cte2 x )