Combina più risultati in una sottoquery in un unico valore separato da virgole


84

Ho due tavoli:

TableA
------
ID,
Name

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

La relazione è una fila di TableA... molti di TableB.

Ora, voglio vedere un risultato come questo:

ID     Name      SomeColumn

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

Questo non funzionerà (più risultati in una sottoquery):

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

Questo è un problema banale se eseguo l'elaborazione dal lato client. Ma questo significa che dovrò eseguire X query su ogni pagina, dove X è il numero di risultati di TableA.

Nota che non posso semplicemente fare un GROUP BY o qualcosa di simile, poiché restituirà più risultati per le righe di TableA.

Non sono sicuro se un UDF, utilizzando COALESCE o qualcosa di simile potrebbe funzionare?

Risposte:


135

Anche questo servirà allo scopo

Dati di esempio

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'

Query:

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

Produzione:

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

13
Non sono sicuro del motivo per cui questo non è stato rilevato in quanto risolve il problema senza richiedere una funzione utente. Puoi vedere la stessa idea espressa qui codecorner.galanter.net/2009/06/25/… che precede questa risposta e quindi potrebbe essere l '"originale"
Paul D'Ambra

1
Lo stesso qui, non sono sicuro del motivo per cui questo non è valutato più alto
Marcel

1
Ciao priyanka, puoi dirmi se e perché la clausola "and t1.name = t2.name" è necessaria qui?
Koen

2
Questo è eccellente. Stavo cercando di ottimizzare una funzione UDF come elencato nella risposta accettata che stava uccidendo il mio server. Sono passato da una ricerca di 102 secondi a meno di 1. Il confronto del piano di esecuzione è stato del 78% -22% ma ciò non si riferisce al tempo di esecuzione ...
toxaq

Solo un promemoria che ti serve quella "" iniziale, altrimenti finirai con le parentesi angolari nel tuo output.
Tim Scarborough

45

1. Crea l'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. Usa nella sottoquery:

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

3. Se stai utilizzando la procedura memorizzata, puoi procedere in questo modo:

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

1
Sembra ancora un trucco. Sto ancora usando le sottoquery, quindi c'è ancora molta elaborazione extra in corso. Sono sicuro che esista una soluzione migliore (ristrutturazione del tavolo o un altro modo di considerare il problema).
Donnie Thomas,

1
Non lo definirei un hack. È più efficiente di un cursore e non ha l'overhead necessario per creare una tabella temporanea con i dati strutturati nel modo desiderato.
Scott Lawrence

1
Peccato che le colonne non possano essere parametri. Così com'è, dovrai creare una funzione per ogni relazione tra figli!
John Paul Jones

1
Va bene: devo combinare solo queste particolari colonne. Il resto sono unioni "tradizionali".
Donnie Thomas

Non ricordo un modo migliore per farlo senza questo metodo.
aF.

11

Penso che tu sia sulla strada giusta con COALESCE. Vedi qui per un esempio di creazione di una stringa delimitata da virgole:

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


2
Eccezionale! Avevo visto alcuni collegamenti che parlavano di COALESCE, ma implicavano la creazione di UDF con trigger. Il collegamento che hai inviato ha la chiave, con una singola istruzione SELECT. Aggiungo una risposta con la soluzione corretta, in modo che sia più facile da trovare per gli altri. Grazie!
Donnie Thomas,

1
Ciao Ben, penso che la risposta richieda un po 'più di dettagli, vale a dire come creare l'UDF, ecc. Una volta capito, aggiungerò la soluzione come risposta modificabile dalla comunità. Sentiti libero di modificarlo, dopodiché lo accetterò come risposta. Dispiace per la confusione.
Donnie Thomas,

11

In MySQL c'è una funzione group_concat che restituirà ciò che stai chiedendo.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID

1
Sarebbe stato perfetto, se ci fosse una funzione simile in SQL Server. Allo stato attuale, sto usando la soluzione di Ben per mettere insieme quello che voglio.
Donnie Thomas,

0

Potrebbe essere necessario fornire ulteriori dettagli per una risposta più precisa.

Poiché il tuo set di dati sembra un po 'ristretto, potresti prendere in considerazione l'utilizzo di una riga per risultato ed eseguire la post-elaborazione sul client.

Quindi, se stai davvero cercando di far fare il lavoro al server, restituisci un set di risultati come

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

che ovviamente è un semplice INNER JOIN su ID

Una volta che hai di nuovo il set di risultati sul client, mantieni una variabile chiamata CurrentName e usala come trigger quando smetti di raccogliere SomeColumn nella cosa utile che vuoi che faccia.


Ci ho pensato, ma non ero molto sicuro che si trattasse di una soluzione elegante: mi piacerebbe che SQL Server restituisse il set di risultati costruito correttamente, non qualcosa che dovrà essere elaborato ulteriormente. Desideri ulteriori dettagli? Ho semplificato la struttura della tabella, ma penso che tu ce l'abbia.
Donnie Thomas,

0

Supponendo che tu abbia solo le clausole WHERE sulla tabella A, crea una procedura memorizzata in questo modo:

SELECT Id, Name From tableA WHERE ...

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

Quindi riempire un DataSet ds con esso. Poi

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

Infine puoi aggiungere un ripetitore nella pagina che inserisce le virgole per ogni riga

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

In questo modo lo farai lato client ma con una sola query, passando dati minimi tra database e frontend


0

Ho provato la soluzione menzionata da priyanka.sarkar e non ha funzionato come richiesto dall'OP. Ecco la soluzione che ho trovato:

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

-1

Soluzione sotto:

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

Usa questo, puoi anche cambiare i join


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

Questo funzionerà per la selezione da una tabella diversa utilizzando la sottoquery.


-1

Ho esaminato tutte le risposte. Penso che nell'inserimento del database dovrebbe essere come:

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

La virgola dovrebbe essere all'estremità precedente ed eseguire la ricerca in base a like %,X,%

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.