Połącz wiele wyników w podzapytaniu w jedną wartość oddzieloną przecinkami

84

Mam dwa stoliki:

TableA
------
ID,
Name

TableB
------
ID,
SomeColumn,
TableA_ID (FK for TableA)

Relacja to jeden wiersz TableA- wiele z nich TableB.

Teraz chcę zobaczyć taki wynik:

ID     Name      SomeColumn

1.     ABC       X, Y, Z (these are three different rows)
2.     MNO       R, S

To nie zadziała (wiele wyników w podzapytaniu):

SELECT ID,
       Name, 
       (SELECT SomeColumn FROM TableB WHERE F_ID=TableA.ID)
FROM TableA

Jest to trywialny problem, jeśli wykonuję przetwarzanie po stronie klienta. Ale to oznacza, że ​​będę musiał uruchamiać zapytania X na każdej stronie, gdzie X to liczba wyników TableA.

Zauważ, że nie mogę po prostu wykonać GROUP BY lub czegoś podobnego, ponieważ zwróci wiele wyników dla wierszy TableA.

Nie jestem pewien, czy UDF, wykorzystujący COALESCE lub coś podobnego może zadziałać?

Donnie Thomas
źródło

Odpowiedzi:

134

Nawet to spełni swoje zadanie

Przykładowe dane

declare @t table(id int, name varchar(20),somecolumn varchar(MAX))
insert into @t
    select 1,'ABC','X' union all
    select 1,'ABC','Y' union all
    select 1,'ABC','Z' union all
    select 2,'MNO','R' union all
    select 2,'MNO','S'

Pytanie:

SELECT ID,Name,
    STUFF((SELECT ',' + CAST(T2.SomeColumn AS VARCHAR(MAX))
     FROM @T T2 WHERE T1.id = T2.id AND T1.name = T2.name
     FOR XML PATH('')),1,1,'') SOMECOLUMN
FROM @T T1
GROUP BY id,Name

Wynik:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
priyanka.sarkar
źródło
13
Nie wiem, dlaczego nie zostało to odebrane, ponieważ rozwiązuje problem bez konieczności wykonywania funkcji użytkownika. Możesz zobaczyć ten sam pomysł wyrażony tutaj codecorner.galanter.net/2009/06/25/ ... który poprzedza tę odpowiedź i może być „oryginał”
Paul D'Ambra
1
To samo tutaj, nie jestem pewien, dlaczego nie jest to ocenione wyżej
Marcel
1
Cześć Priyanka, czy możesz mi powiedzieć, czy i dlaczego klauzula „i t1.name = t2.name” jest tutaj konieczna?
Koen,
2
To jest doskonałe. Chciałem zoptymalizować funkcję UDF wymienioną w zaakceptowanej odpowiedzi, która zabijała mój serwer. Przeszedłem od 102 sekund wyszukiwania do mniej niż 1. Porównanie planu wykonania wyniosło 78% -22%, ale to nie dotyczy czasu wykonania ...
toxaq
Przypominamy tylko, że będziesz potrzebować tego początkowego znaku „,” w przeciwnym razie w wyniku otrzymasz nawiasy kątowe.
Tim Scarborough,
45

1. Utwórz UDF:

CREATE FUNCTION CombineValues
(
    @FK_ID INT -- The foreign key from TableA which is used 
               -- to fetch corresponding records
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE @SomeColumnList VARCHAR(8000);

SELECT @SomeColumnList =
    COALESCE(@SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20)) 
FROM TableB C
WHERE C.FK_ID = @FK_ID;

RETURN 
(
    SELECT @SomeColumnList
)
END

2. Użyj w podzapytaniu:

SELECT ID, Name, dbo.CombineValues(FK_ID) FROM TableA

3. Jeśli korzystasz z procedury składowanej, możesz to zrobić:

CREATE PROCEDURE GetCombinedValues
 @FK_ID int
As
BEGIN
DECLARE @SomeColumnList VARCHAR(800)
SELECT @SomeColumnList =
    COALESCE(@SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20)) 
FROM TableB
WHERE FK_ID = @FK_ID 

Select *, @SomeColumnList as SelectedIds
    FROM 
        TableA
    WHERE 
        FK_ID = @FK_ID 
END
Donnie Thomas
źródło
1
To wciąż wygląda jak włamanie. Nadal używam podzapytań, więc wciąż jest dużo dodatkowego przetwarzania. Jestem pewien, że istnieje lepsze rozwiązanie (restrukturyzacja stołu lub inne spojrzenie na problem).
Donnie Thomas
1
Nie nazwałbym tego hackem. Jest bardziej wydajny niż kursor i nie ma narzutu, który byłby potrzebny do utworzenia tymczasowej tabeli z danymi uporządkowanymi tak, jak chcesz.
Scott Lawrence
1
Szkoda, że ​​kolumny nie mogą być parametrami. W obecnej postaci musisz pełnić funkcję dla każdego związku dziecka!
John Paul Jones,
1
W porządku - muszę połączyć tylko te konkretne kolumny. Reszta to łączenia „tradycyjne”.
Donnie Thomas,
Nie przypominam sobie najlepszego sposobu na zrobienie tego bez tej metody.
aF.
11

Myślę, że z COALESCE jesteś na dobrej drodze. Zobacz tutaj, aby zobaczyć przykład tworzenia ciągu rozdzielanego przecinkami:

http://www.sqlteam.com/article/using-coalesce-to-build-comma-delimited-string

Ben Hoffstein
źródło
2
Niesamowite! Widziałem kilka linków omawiających COALESCE, ale obejmowały one tworzenie UDF z wyzwalaczami. Przesłany link ma klucz z pojedynczą instrukcją SELECT. Dodaję odpowiedź z poprawnym rozwiązaniem, aby inni mogli ją łatwiej znaleźć. Dzięki!
Donnie Thomas,
1
Cześć Ben, myślę, że odpowiedź wymaga nieco więcej szczegółów, a mianowicie, jak utworzyć UDF, itp. Kiedy to zrozumiem, dodam rozwiązanie jako odpowiedź edytowalną przez społeczność. Zapraszam do edycji, po czym zaakceptuję to jako odpowiedź. Przepraszam za zamieszanie.
Donnie Thomas,
11

W MySQL jest funkcja group_concat , która zwraca to, o co prosisz.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID
Jakub
źródło
1
Byłoby idealnie, gdyby podobna funkcja istniała w SQL Server. W obecnym stanie używam rozwiązania Bena, aby połączyć to, co chcę.
Donnie Thomas
0

Być może będziesz musiał podać więcej szczegółów, aby uzyskać bardziej precyzyjną odpowiedź.

Ponieważ zestaw danych wydaje się być wąski, możesz rozważyć użycie wiersza dla każdego wyniku i wykonanie przetwarzania końcowego na kliencie.

Więc jeśli naprawdę chcesz, aby serwer wykonał pracę, zwróci zestaw wyników, taki jak

ID       Name       SomeColumn
1        ABC        X
1        ABC        Y
1        ABC        Z
2        MNO        R
2        MNO        S

co oczywiście jest prostym WEWNĘTRZNYM DOŁĄCZENIEM na ID

Gdy już masz zestaw wyników z powrotem na kliencie, utrzymuj zmienną o nazwie CurrentName i użyj jej jako wyzwalacza, kiedy przestaniesz zbierać SomeColumn do użytecznej rzeczy, którą chcesz, aby zrobiła.

Rachunek
źródło
Myślałem o tym, ale nie byłem pewien, czy to eleganckie rozwiązanie - chciałbym, aby SQL Server zwracał odpowiednio skonstruowany zestaw wyników, a nie coś, co trzeba będzie dalej przetwarzać. Czy potrzebujesz dodatkowych informacji? Uprościłem strukturę tabeli, ale myślę, że masz to.
Donnie Thomas,
0

Zakładając, że masz tylko klauzule WHERE w tabeli A, utwórz procedurę składowaną w ten sposób:

SELECT Id, Name From tableA WHERE ...

SELECT tableA.Id AS ParentId, Somecolumn 
FROM tableA INNER JOIN tableB on TableA.Id = TableB.F_Id 
WHERE ...

Następnie wypełnij nim DataSet ds. Następnie

ds.Relations.Add("foo", ds.Tables[0].Columns("Id"), ds.Tables[1].Columns("ParentId"));

Na koniec możesz dodać repeater na stronie, który umieszcza przecinki dla każdej linii

 <asp:DataList ID="Subcategories" DataKeyField="ParentCatId" 
DataSource='<%# Container.DataItem.CreateChildView("foo") %>' RepeatColumns="1"
 RepeatDirection="Horizontal" ItemStyle-HorizontalAlign="left" ItemStyle-VerticalAlign="top" 
runat="server" >

W ten sposób zrobisz to po stronie klienta, ale za pomocą tylko jednego zapytania, przekazując minimum danych między bazą danych a frontendem

Sklivvz
źródło
0

Wypróbowałem rozwiązanie, o którym wspomniał priyanka.sarkar, ale nie działało ono tak, jak prosił OP. Oto rozwiązanie, które otrzymałem:

SELECT ID, 
        SUBSTRING((
            SELECT ',' + T2.SomeColumn
            FROM  @T T2 
            WHERE WHERE T1.id = T2.id
            FOR XML PATH('')), 2, 1000000)
    FROM @T T1
GROUP BY ID
mrogunlana
źródło
-1

Rozwiązanie poniżej:

SELECT GROUP_CONCAT(field_attr_best_weekday_value)as RAVI
FROM content_field_attr_best_weekday LEFT JOIN content_type_attraction
    on content_field_attr_best_weekday.nid = content_type_attraction.nid
GROUP BY content_field_attr_best_weekday.nid

Użyj tego, możesz również zmienić połączenia

ravi
źródło
-1
SELECT t.ID, 
       t.NAME, 
       (SELECT t1.SOMECOLUMN 
        FROM   TABLEB t1 
        WHERE  t1.F_ID = T.TABLEA.ID) 
FROM   TABLEA t; 

To zadziała przy wybieraniu z innej tabeli za pomocą zapytania podrzędnego.

ATHAR
źródło
-1

Przejrzałem wszystkie odpowiedzi. Myślę, że wstawianie do bazy danych powinno wyglądać następująco:

ID     Name      SomeColumn
1.     ABC       ,X,Y Z (these are three different rows)
2.     MNO       ,R,S

Przecinek powinien znajdować się na poprzednim końcu i wyszukiwać podobnie %,X,%

rsda
źródło