2015-06-06 11 views
6

Mam problem z trudnym zapytaniem SQL, które próbuję napisać. Przyjrzeć się poniższej tabeli:T-SQL - Zdobądź listę wszystkich As, które mają ten sam zestaw Bs

+---+---+ 
| A | B | 
+---+---+ 
| 1 | 2 | 
| 1 | 3 | 
| 2 | 2 | 
| 2 | 3 | 
| 2 | 4 | 
| 3 | 2 | 
| 3 | 3 | 
| 4 | 2 | 
| 4 | 3 | 
| 4 | 4 | 
+---+---+ 

Teraz, z tego stołu, zasadniczo ma listę wszystkich jako które mają dokładnie ten sam zestaw B i dać każdemu ustawić wzrastających ID.

Stąd wyjście ustawione na powyższe byłoby:

+---+----+ 
| A | ID | 
+---+----+ 
| 1 | 1 | 
| 3 | 1 | 
| 2 | 2 | 
| 4 | 2 | 
+---+----+ 

Dzięki.

Edycja: Jeśli to pomaga, mam listę wszystkich różnych wartości B, które są możliwe w innej tabeli.

Edytuj: Dziękuję bardzo za wszystkie innowacyjne odpowiedzi. Mogłem się wiele nauczyć.

+0

Gdybyś miał 3 kolejne rzędy: '4,2 | 4,3 | 4,4'? – edc65

+0

Myślałem o zrobieniu odrębnej listy jako, a następnie przechowywaniu listy CSV Bs dla każdego A w tabeli temp. Wreszcie znajdę wpisy w tabeli, które mają te same ciągi CSV. –

+0

Istnieje Commn Trick do budowania ciągów CSV za pomocą funkcji xml, nie potrzebują tabeli temp: http://sqlandme.com/2011/04/27/tsql-concatenate-rows-using-for-xml-path/ – edc65

Odpowiedz

5

Oto matematyczny trik, aby rozwiązać swoje trudne wybierz:

with pow as(select *, b * power(10, row_number() 
       over(partition by a order by b)) as rn from t) 
select a, dense_rank() over(order by sum(rn)) as rn 
from pow 
group by a 
order by rn, a 

Fiddle http://sqlfiddle.com/#!3/6b98d/11

To oczywiście będzie działać tylko dla ograniczonej liczby odrębnego jak dostaniesz przepełnienie. Tutaj jest bardziej ogólne rozwiązanie z ciągów:

select a, 
dense_rank() over(order by (select '.' + cast(b as varchar(max)) 
          from t t2 where t1.a = t2.a 
          order by b 
          for xml path(''))) rn 
from t t1 
group by a 
order by rn, a 

Fiddle http://sqlfiddle.com/#!3/6b98d/29

+3

Mimo że przegłosowano, należy zmienić dwie rzeczy. Po pierwsze, naprawdę potrzebujesz separatora dla wartości 'b' w łańcuchu (ile elementów znajduje się w" 123 "). Po drugie, nigdy nie powinieneś używać 'varchar()' bez parametru długości w SQL Server. Domyślna długość zależy od kontekstu; bycie jawnym zapobiega dalszym błędom. –

+0

@GordonLinoff, masz absolutną rację. 123 i 12 i 3 dałyby taką samą rangę. Dobry połów! –

2

EDIT mam zmianę kodu, ale będzie to coraz większy teraz, wziął pomoc Concatenate many rows into a single text string? dla concatinating ciągów

Select [A], 
    Left(M.[C],Len(M.[C])-1) As [D] into #tempSomeTable 
From 
(
    Select distinct T2.[A], 
     (
      Select Cast(T1.[B] as VARCHAR) + ',' AS [text()] 
      From sometable T1 
      Where T1.[A] = T2.[A] 
      ORDER BY T1.[A] 
      For XML PATH ('') 
     ) [C] 
    From sometable T2 
)M 

    SELECT t.A, DENSE_RANK() OVER(ORDER BY t.[D]) [ID] FROM 
    #tempSomeTable t 
    inner join 
    (SELECT [D] FROM(
    SELECT [D], COUNT([A]) [D_A] from 
    #tempSomeTable t 
    GROUP BY [D])P where [C_A]>1)t1 on t1.[D]=t.[D] 
+0

Suma? spróbuj z '2,4,6' i' 1,3,8' – edc65

+0

Dzięki, zmieniłeś go na porównanie ciągów. – debatanu

+0

Nie działa teraz – edc65

2

Oto długo zdyszany podejście, poprzez znalezienie zestawy z tych samych elementów (używając dwukierunkowo, aby wyeliminować, i właśnie wykonano półkulistyki produkt kartezjański), a następnie sparowałeś równo, stemplując każdą parę z ROW_NUMBER(), przed rozpakowaniem par A's do końcowego wydruku, gdzie równoważne zestawy są rzutowane jako wiersze, które mają sa ja id.

WITH joinedSets AS 
(
    SELECT t1.A as t1A, t2.A AS t2A 
    FROM MyTable t1 
    INNER JOIN MyTable t2 
    ON t1.B = t2.B 
     AND t1.A < t2.A 
), 
equalSets AS 
(
    SELECT js.t1A, js.t2A, ROW_NUMBER() OVER (ORDER BY js.t1A) AS Id 
    FROM joinedSets js 
    GROUP BY js.t1A, js.t2A 
    HAVING NOT EXISTS ((SELECT mt.B FROM MyTable mt WHERE mt.A = js.t1A) 
      EXCEPT (SELECT mt.B FROM MyTable mt WHERE mt.A = js.t2A)) 
     AND NOT EXISTS ((SELECT mt.B FROM MyTable mt WHERE mt.A = js.t2A) 
      EXCEPT (SELECT mt.B FROM MyTable mt WHERE mt.A = js.t1A)) 
) 
SELECT A, Id 
FROM equalSets 
UNPIVOT 
(
    A 
    FOR ACol in (t1A, t2A) 
) unp; 

SqlFiddle here

W obecnej formie, to rozwiązanie będzie działać tylko z parami zestawów, a nie trójki itd ogólnym NTuple typu rozwiązanie jest chyba możliwe (ale poza moim mózgu teraz).

3

coś takiego:

select a, dense_rank() over (order by g) as id_b 
from (
    select a, 
    (select b from MyTable s where s.a=a.a order by b FOR XML PATH('')) g 
    from MyTable a 
    group by a 
) a 
order by id_b,a 

Albo przy użyciu CTE (ja ich unikać, jeśli to możliwe)

Sql Fiddle

Na marginesie, to jest obecnie wstawienie zapytania wewnętrznego z użyciem przykładowych danych w pytaniu:

a g 
1 <b>2</b><b>3</b> 
2 <b>2</b><b>3</b><b>4</b> 
3 <b>2</b><b>3</b> 
4 <b>2</b><b>3</b><b>4</b> 
+0

Numer wiersza nie da odpowiedniego zestawu wyników, jeśli się nie mylę. –

+0

Miałeś rację, przepraszam. Myślę, że można to zrobić za pomocą row_number, ale dense_rank lepiej pasuje. – edc65

+0

Zobacz mój komentarz równoważny rozwiązanie mojego komentarza @ Giorgi. –

2

Oto bardzo proste, szybkie, ale przybliżone rozwiązanie. Jest możliwe, że CHECKSUM_AGG zwraca samą kontrolną dla różnych zestawów B.

DECLARE @T TABLE (A int, B int); 

INSERT INTO @T VALUES 
(1, 2),(1, 3),(2, 2),(2, 3),(2, 4),(3, 2),(3, 3),(4, 2),(4, 3),(4, 4); 

SELECT 
    A 
    ,CHECKSUM_AGG(B) AS CheckSumB 
    ,ROW_NUMBER() OVER (PARTITION BY CHECKSUM_AGG(B) ORDER BY A) AS GroupNumber 
FROM @T 
GROUP BY A 
ORDER BY A, GroupNumber; 

wynikowej,

A CheckSumB GroupNumber 
----------------------------- 
1 1   1 
2 5   1 
3 1   2 
4 5   2 

Dokładne grupa rozwiązań przez A i łączyć wszystkie wartości B na długi (binarnie) ciąg znaków przy użyciu funkcji FOR XML, CLR lub T-SQL. Następnie można podzielić ROW_NUMBER przez ten połączony ciąg, aby przypisać numery do grup. Jak pokazano w innych odpowiedziach.

0

Oto dokładne, a nie przybliżone rozwiązanie. Używa niczego bardziej zaawansowanego niż INNER JOIN i GROUP BY (i, oczywiście, DENSE_RANK(), aby uzyskać ID, który chcesz).

Jest także ogólna, ponieważ umożliwia powtarzanie wartości B w grupie A.

SELECT A, 
     DENSE_RANK() OVER (ORDER BY MIN_EQUIVALENT_A) AS ID 

FROM  (
      SELECT MATCHES.A1 AS A, 
        MIN(MATCHES.A2) AS MIN_EQUIVALENT_A 

      FROM  (
        SELECT T1.A AS A1, 
          T2.A AS A2, 
          COUNT(*) AS NUM_B_VALS_MATCHED 

        FROM  (
           SELECT A, 
             B, 
             COUNT(*) AS B_VAL_FREQ 
           FROM  MyTable 
           GROUP BY A, 
             B 
          ) AS T1 

          INNER JOIN 

          (
           SELECT A, 
             B, 
             COUNT(*) AS B_VAL_FREQ 
           FROM  MyTable 
           GROUP BY A, 
             B 
          ) AS T2 

          ON T1.B = T2.B 
           AND T1.B_VAL_FREQ = T2.B_VAL_FREQ 

        GROUP BY T1.A, 
          T2.A 
        ) AS MATCHES 

        INNER JOIN 

        (
        SELECT A, 
          COUNT(DISTINCT B) AS NUM_B_VALS_TOTAL 
        FROM  MyTable 
        GROUP BY A 
        ) AS CHECK_TOTALS_A1 

        ON MATCHES.A1 = CHECK_TOTALS_A1.A 
         AND MATCHES.NUM_B_VALS_MATCHED 
          = CHECK_TOTALS_A1.NUM_B_VALS_TOTAL 

        INNER JOIN 

        (
        SELECT A, 
          COUNT(DISTINCT B) AS NUM_B_VALS_TOTAL 
        FROM  MyTable 
        GROUP BY A 
        ) AS CHECK_TOTALS_A2 

        ON MATCHES.A2 = CHECK_TOTALS_A2.A 
         AND MATCHES.NUM_B_VALS_MATCHED 
          = CHECK_TOTALS_A2.NUM_B_VALS_TOTAL 

      GROUP BY MATCHES.A1 
     ) AS EQUIVALENCE_TABLE 

ORDER BY 2,1 
;