Kombinieren Sie mehrere Ergebnisse in einer Unterabfrage zu einem einzigen durch Kommas getrennten Wert

84

Ich habe zwei Tische:

TableA
------
ID,
Name

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

Die Beziehung ist eine Reihe von TableA- vielen von TableB.

Jetzt möchte ich ein Ergebnis wie dieses sehen:

ID     Name      SomeColumn

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

Dies funktioniert nicht (mehrere Ergebnisse in einer Unterabfrage):

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

Dies ist ein triviales Problem, wenn ich die Verarbeitung auf der Client-Seite durchführe. Dies bedeutet jedoch, dass ich auf jeder Seite X-Abfragen ausführen muss, wobei X die Anzahl der Ergebnisse von ist TableA.

Beachten Sie, dass ich nicht einfach ein GROUP BY oder ähnliches durchführen kann, da es mehrere Ergebnisse für Zeilen von zurückgibt TableA.

Ich bin mir nicht sicher, ob eine UDF mit COALESCE oder ähnlichem funktionieren könnte.

Donnie Thomas
quelle

Antworten:

134

Auch dies wird den Zweck erfüllen

Beispieldaten

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'

Abfrage:

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

Ausgabe:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
priyanka.sarkar
quelle
13
Ich bin mir nicht sicher, warum dies nicht erkannt wurde, da es das Problem löst, ohne dass eine Benutzerfunktion erforderlich ist. Sie können die gleiche Idee sehen, die hier zum Ausdruck gebracht wird codecorner.galanter.net/2009/06/25/…, die vor dieser Antwort liegt und möglicherweise das "Original" ist
Paul D'Ambra
1
Gleich hier, nicht sicher, warum dies nicht höher bewertet wird
Marcel
1
Hallo Priyanka, kannst du mir sagen, ob und warum die Klausel "und t1.name = t2.name" hier notwendig ist?
Koen
2
Das ist ausgezeichnet. Ich wollte eine UDF-Funktion optimieren, wie in der akzeptierten Antwort aufgeführt, die meinen Server zum Erliegen brachte. Ich ging von einer 102-Sekunden-Suche auf weniger als 1 zurück. Der Vergleich des Ausführungsplans betrug 78% -22%, aber das bezieht sich nicht auf die Ausführungszeit ...
toxaq
Nur eine Erinnerung daran, dass Sie dieses führende '' benötigen, sonst erhalten Sie spitze Klammern in Ihrer Ausgabe.
Tim Scarborough
45

1. Erstellen Sie die 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. Verwendung in Unterabfrage:

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

3. Wenn Sie eine gespeicherte Prozedur verwenden, können Sie Folgendes tun:

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
quelle
1
Das fühlt sich immer noch wie ein Hack an. Ich verwende immer noch Unterabfragen, daher wird noch viel zusätzliche Verarbeitung durchgeführt. Ich bin sicher, dass es eine bessere Lösung gibt (Tabellenumstrukturierung oder eine andere Sichtweise auf das Problem).
Donnie Thomas
1
Ich würde das nicht Hack nennen. Es ist effizienter als ein Cursor und es fehlt der Overhead, der erforderlich wäre, um eine temporäre Tabelle mit den Daten zu erstellen, die so strukturiert sind, wie Sie es möchten.
Scott Lawrence
1
Schade, dass die Spalten keine Parameter sein können. So wie es aussieht, müssen Sie für jede Kinderbeziehung eine Funktion erstellen!
John Paul Jones
1
Das ist in Ordnung - ich muss nur diese bestimmten Spalten kombinieren. Der Rest sind "traditionelle" Verbindungen.
Donnie Thomas
Ich erinnere mich nicht an einen besten Weg, dies ohne diese Methode zu tun.
aF.
11

Ich denke, Sie sind mit COALESCE auf dem richtigen Weg. Hier finden Sie ein Beispiel für die Erstellung einer durch Kommas getrennten Zeichenfolge:

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

Ben Hoffstein
quelle
2
Genial! Ich hatte einige Links gesehen, die sich mit COALESCE befassten, aber sie beinhalteten das Erstellen von UDFs mit Triggern. Der von Ihnen übermittelte Link enthält den Schlüssel mit einer einzelnen SELECT-Anweisung. Ich füge eine Antwort mit der richtigen Lösung hinzu, damit andere sie leichter finden können. Vielen Dank!
Donnie Thomas
1
Hallo Ben, ich denke, die Antwort muss etwas detaillierter sein, nämlich wie man die UDF erstellt usw. Sobald ich das herausgefunden habe, werde ich die Lösung als von der Community bearbeitbare Antwort hinzufügen. Bitte zögern Sie nicht, es zu bearbeiten, danach akzeptiere ich das als Antwort. Entschuldigung für die Verwirrung.
Donnie Thomas
11

In MySQL gibt es eine group_concat- Funktion, die zurückgibt , wonach Sie fragen.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID
Jacob
quelle
1
Dies wäre perfekt gewesen, wenn es eine ähnliche Funktion in SQL Server gegeben hätte. So wie es aussieht, verwende ich Bens Lösung, um zusammenzuschlagen, was ich will.
Donnie Thomas
0

Möglicherweise müssen Sie weitere Details angeben, um eine genauere Antwort zu erhalten.

Da Ihr Datensatz etwas eng erscheint, sollten Sie möglicherweise nur eine Zeile pro Ergebnis verwenden und die Nachbearbeitung auf dem Client durchführen.

Wenn Sie also wirklich möchten, dass der Server die Arbeit erledigt, geben Sie eine Ergebnismenge wie zurück

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

Das ist natürlich eine einfache INNER JOIN on ID

Wenn Sie die Ergebnismenge wieder auf dem Client haben, pflegen Sie eine Variable namens CurrentName und verwenden Sie diese als Auslöser, wenn Sie die Erfassung von SomeColumn für die nützliche Aufgabe beenden möchten.

Rechnung
quelle
Ich habe darüber nachgedacht, war mir aber nicht sicher, ob dies eine elegante Lösung ist. Ich möchte, dass SQL Server die ordnungsgemäß erstellte Ergebnismenge zurückgibt und nicht weiter verarbeitet werden muss. Würden Sie zusätzliche Details benötigen? Ich habe die Tabellenstruktur vereinfacht, aber ich denke, Sie haben es.
Donnie Thomas
0

Angenommen, Sie haben nur WHERE-Klauseln in Tabelle A, erstellen Sie eine gespeicherte Prozedur wie folgt:

SELECT Id, Name From tableA WHERE ...

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

Füllen Sie dann ein DataSet ds damit. Dann

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

Schließlich können Sie der Seite einen Repeater hinzufügen, der die Kommas für jede Zeile setzt

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

Auf diese Weise erledigen Sie dies clientseitig, jedoch mit nur einer Abfrage, wobei nur minimale Daten zwischen Datenbank und Frontend übertragen werden

Sklivvz
quelle
0

Ich habe die von priyanka.sarkar erwähnte Lösung ausprobiert und sie hat es nicht ganz zum Laufen gebracht, als das OP darum bat. Hier ist die Lösung, mit der ich gelandet bin:

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
quelle
-1

Lösung unten:

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

Mit dieser Option können Sie auch die Joins ändern

Ravi
quelle
-1
SELECT t.ID, 
       t.NAME, 
       (SELECT t1.SOMECOLUMN 
        FROM   TABLEB t1 
        WHERE  t1.F_ID = T.TABLEA.ID) 
FROM   TABLEA t; 

Dies funktioniert für die Auswahl aus verschiedenen Tabellen mithilfe der Unterabfrage.

ATHAR
quelle
-1

Ich habe alle Antworten überprüft. Ich denke beim Einfügen von Datenbanken sollte es so aussehen:

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

Das Komma sollte am vorherigen Ende stehen und nach "Gefällt mir" suchen %,X,%

rsda
quelle