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
5
  • 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. Commented Jan 22, 2016 at 1:49
  • 1
    I'd suggest just making ascii tables using something like ozh.github.io/ascii-tables Commented Jan 22, 2016 at 2:01
  • Sorry it was by mistake just edited so it's showing correct output now... Commented Jan 22, 2016 at 2:21
  • Does it need to be a view, or are you working in the context of a stored procedure? Commented Jan 22, 2016 at 2:24
  • either way fine but I think so in my case I will be using stored procedure. Commented Jan 22, 2016 at 2:26

4 Answers 4

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

Sign up to request clarification or add additional context in comments.

1 Comment

Clever answer. The path can be of any format, and is required to stop the recursive loop.
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!

3 Comments

This will work as long as the table is small. Performance will be a problem. O(x^2)
Yes indeed. I'm hoping someone will come along with a "proper" solution.
I think this is the best OP can do. SQL is not the right language for this, IMO
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/

Comments

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 )

2 Comments

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 ...
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. sqlfiddle.com/#!6/c641f4/1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.