6

I've a question about the use of recursive SQL in which I have following table structure

Products can be in multiple groups (for the sake of clarity, I am not using int )

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 | 
+-----------+---------+

Now the Question is I want to find out all the related products so i.e. if I pass Product 1 then I need the following result

+-----------+---------+
|  Product  |  Group  |
+-----------+---------+
| Product 1 | Group 1 |
| Product 1 | Group 2 |
| Product 2 | Group 1 |
| Product 2 | Group 6 |    
| Product 4 | Group 6 | 
+-----------+---------+

So basically I want to first find out all the Groups for product 1 and then for each group I want to find out all the products and so on...

  1. Product 1 => Group 1, Group 2;
  2. Group 1 => Product 1, Product 2 (Group 1 and Product 1 already exist so should be avoided otherwise would go into infinite loop);
  3. Group 2 => Product 1 (already exist so same as above);
  4. Product 2 => Group 1, Group 6 (Group 1 and Product 2 already exist)
  5. Group 6 => Product 4
Tejas Patel
  • 1,289
  • 13
  • 25
  • 1
    I don't think the snippet syntax is a good idea in this context, even if the output you want is formatted as HTML. I was very tempted to just remove it, but I'm not sure what significance attaches to the CSS section of the output. I think it should be omitted, but it is your question. – Jonathan Leffler Jan 22 '16 at 01:49
  • 1
    I'd suggest just making ascii tables using something like https://ozh.github.io/ascii-tables/ – Blorgbeard Jan 22 '16 at 02:01
  • Sorry it was by mistake just edited so it's showing correct output now... – Tejas Patel Jan 22 '16 at 02:21
  • Does it need to be a view, or are you working in the context of a stored procedure? – Greg Viers Jan 22 '16 at 02:24
  • either way fine but I think so in my case I will be using stored procedure. – Tejas Patel Jan 22 '16 at 02:26

4 Answers4

4

It can be done with a recursive query, but it's not optimal because SQL Server does not allow you to reference the recursive table as a set. So you end up having to keep a path string to avoid infinite loops. If you use ints you can replace the path string with a 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

gordy
  • 9,360
  • 1
  • 31
  • 43
3

I don't think this is possible with a recursive CTE, because you're only allowed one recursive reference per recursive definition.

I did manage to implement it with a while loop, which is likely to be less efficient than the 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

You should definitely be careful with the above - benchmark on real sized data before deploying!

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
2

This is not going to be easy. You are modelling equivalence classes. SQL is a set langauge, and you are looking at a class - a set of sets:

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

Greg Viers
  • 3,473
  • 3
  • 18
  • 36
2

You can do this.

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 )
Robert Paulsen
  • 4,935
  • 3
  • 21
  • 27
  • Doesn't quite work with following scenario +-----------+---------+ | 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 | | Product 4 | Group 7 | +-----------+---------+ for above scenario, It should provide exactly same output as input ... – Tejas Patel Jan 25 '16 at 05:20
  • My apologies as above comment is not that readable but comments does not allow ascii table. So if we add one more record in input table as (Product 4 and Group 7) then all 8 records should be shown in output but the solution you provided only shows 5 rows. http://sqlfiddle.com/#!6/c641f4/1 – Tejas Patel Jan 25 '16 at 05:32