2016-01-22 38 views
6

Mam pytanie na temat wykorzystania rekurencyjnego SQL, w którym mam następujące struktury tabeliWdrożenie rekurencyjne zapytanie w SQL

Produkty mogą być w wielu grupach (dla jasności, nie używam 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 | 
+-----------+---------+ 

teraz pytanie chcę znaleźć wszystkie produkty związane więc tj jeśli mijam produktu 1 potem muszę następujący wynik

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

Więc zasadniczo Chcę najpierw dowiedzieć się wszystkich grup dla produktu 1, a następnie dla każdej grupy chcę znaleźć wszystkie produkty i tak dalej ...

  1. Produkt 1 => Grupa 1 , Grupa 2;
  2. Grupa 1 => Produkt 1, Produkt 2 (Grupa 1 i Produkt 1 już istnieją, więc należy ich unikać, w przeciwnym razie przechodzi w nieskończoną pętlę );
  3. Grupa 2 => Produkt 1 (już istnieje tak samo jak powyżej);
  4. Produkt 2 => Grupa 1 Grupa 6 (Grupa 1 i 2 produktów istnieje)
  5. Grupa 6 => produktów 4
+1

Nie sądzę, że składnia fragmentu jest dobrym pomysłem w tym kontekście, nawet jeśli wyjście, które chcesz, jest sformatowane jako HTML. Bardzo mnie to kusiło, żeby go usunąć, ale nie jestem pewien, jakie znaczenie przypisuje sekcji CSS w pliku wyjściowym. Myślę, że należy go pominąć, ale to jest twoje pytanie. –

+1

Proponuję właśnie tworzyć tabele ASCII używając czegoś takiego jak https://ozh.github.io/ascii-tables/ – Blorgbeard

+0

Przepraszam, że przez pomyłkę właśnie edytowałem, więc teraz pokazuje poprawne wyjście ... –

Odpowiedz

3

nie, że jest to możliwe w przypadku cyklicznej CTE, ponieważ dozwolone jest tylko jedno odwołanie rekurencyjne dla definicji rekursywnej.

I udało się wdrożyć go z while pętli, która może być mniej efektywne niż 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 

powinno się uważać z powyższym - odniesienia na danych rzeczywistych rozmiarów przed wdrożeniem!

+1

To będzie działać tak długo, jak tabela jest mała. Wydajność będzie problemem. O (x^2) –

+0

Tak, rzeczywiście. Mam nadzieję, że ktoś przyjdzie z "właściwym" rozwiązaniem. – Blorgbeard

+0

Myślę, że to najlepsze OP może zrobić. SQL nie jest właściwym językiem do tego, IMO –

4

To można zrobić z kwerendy cyklicznej, ale to nie jest optymalne, ponieważ SQL Server nie pozwala na odniesienie tabela rekursywna jako zestaw. W efekcie musisz zachować ciąg znaków, aby uniknąć nieskończonych pętli. Jeśli używasz ints, możesz zamienić ciąg znaków na ścieżkę 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

+0

Sprytna odpowiedź. Ścieżka może mieć dowolny format i jest wymagana do zatrzymania pętli rekursywnej. –

2

Można to zrobić.

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

Działa niezupełnie w następującym scenariuszu + ----------- + --------- + | Produkt | Grupa | + ----------- + --------- + | Produkt 1 | Grupa 1 | | Produkt 1 | Grupa 2 | | Produkt 2 | Grupa 1 | | Produkt 2 | Grupa 6 | | Produkt 3 | Grupa 7 | | Produkt 3 | Grupa 8 | | Produkt 4 | Grupa 6 | | Produkt 4 | Grupa 7 | + ----------- + --------- dla powyższego scenariusza, Powinien zapewniać dokładnie to samo wyjście co wejście ... –

+0

Moje przeprosiny, jak powyżej, nie są aż tak czytelne, ale komentarze nie pozwalają na tabelę ascii. Więc jeśli dodamy jeszcze jeden rekord w tabeli wejściowej jako (Produkt 4 i Grupa 7), wszystkie 8 rekordów powinno być pokazanych na wyjściu, ale podane rozwiązanie pokazuje tylko 5 wierszy. http://sqlfiddle.com/#!6/c641f4/1 –