web-dev-qa-db-fra.com

Combinez plusieurs résultats dans une sous-requête en une seule valeur séparée par des virgules

J'ai deux tables:

TableA
------
ID,
Name

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

La relation est une ligne de TableA - beaucoup de TableB.

Maintenant, je veux voir un résultat comme celui-ci:

ID     Name      SomeColumn

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

Cela ne fonctionnera pas (plusieurs résultats dans une sous-requête):

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

C'est un problème trivial si je traite le client. Mais cela signifiera que je devrai exécuter X requêtes sur chaque page, où X est le nombre de résultats de TableA.

Notez que je ne peux pas simplement faire un GROUP BY ou quelque chose de similaire, car cela retournera plusieurs résultats pour les lignes de TableA.

Je ne sais pas si une FDU utilisant COALESCE ou quelque chose de similaire peut fonctionner?

79
Donnie Thomas

1. Créez le fichier 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. Utilisation en sous-requête:

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

3. Si vous utilisez une procédure stockée, vous pouvez procéder comme suit:

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
45
Donnie Thomas

Même cela servira le but

échantillon de données

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'

Requête:

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

Sortie:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
129
priyanka.sarkar

Je pense que vous êtes sur la bonne voie avec COALESCE. Voir ici pour un exemple de construction d'une chaîne délimitée par des virgules:

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

11
Ben Hoffstein

Dans MySQL, il existe une fonction group_concat qui retournera ce que vous demandez.

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

En supposant que vous n’ayez que des clauses WHERE sur la table A, créez une procédure stockée ainsi:

SELECT Id, Name From tableA WHERE ...

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

Puis remplissez un DataSet ds avec. ensuite

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

Enfin, vous pouvez ajouter un répéteur dans la page qui met les virgules pour chaque ligne

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

De cette façon, vous le ferez côté client mais avec une seule requête, en transmettant un minimum de données entre la base de données et le client.

0
Sklivvz

J'ai essayé la solution mentionnée par priyanka.sarkar et elle n'a pas très bien fonctionné comme l'a demandé l'OP. Voici la solution avec laquelle je me suis retrouvé:

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
0
mrogunlana

Vous devrez peut-être fournir plus de détails pour une réponse plus précise.

Étant donné que votre ensemble de données semble assez étroit, vous pouvez envisager d'utiliser simplement une ligne par résultat et d'effectuer le post-traitement sur le client.

Donc, si vous cherchez vraiment à faire en sorte que le serveur fasse le travail, renvoyez un résultat comme

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

qui est bien sûr un simple INNER JOIN on ID

Une fois que vous avez le jeu de résultats chez le client, gérez une variable appelée CurrentName et utilisez-la comme déclencheur pour cesser de collecter SomeColumn dans la tâche utile que vous souhaitez faire.

0
Bill