Combinar varios resultados en una subconsulta en un solo valor separado por comas

84

Tengo dos mesas:

TableA
------
ID,
Name

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

La relación es una fila de TableA- muchas de TableB.

Ahora, quiero ver un resultado como este:

ID     Name      SomeColumn

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

Esto no funcionará (varios resultados en una subconsulta):

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

Este es un problema trivial si hago el procesamiento del lado del cliente. Pero esto significa que tendré que ejecutar X consultas en cada página, donde X es el número de resultados de TableA.

Tenga en cuenta que no puedo simplemente hacer un GROUP BY o algo similar, ya que devolverá múltiples resultados para las filas de TableA.

No estoy seguro de si una UDF, utilizando COALESCE o algo similar podría funcionar.

Donnie Thomas
fuente

Respuestas:

134

Incluso esto servirá al propósito

Data de muestra

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'

Consulta:

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

Salida:

ID  Name    SomeColumn
1   ABC     X,Y,Z
2   MNO     R,S
priyanka.sarkar
fuente
13
No estoy seguro de por qué esto no se ha detectado, ya que resuelve el problema sin requerir una función de usuario. Puede ver la misma idea expresada aquí codecorner.galanter.net/2009/06/25/… que es anterior a esta respuesta y por lo tanto podría ser el "original"
Paul D'Ambra
1
Lo mismo aquí, no estoy seguro de por qué no tiene una calificación más alta
Marcel
1
Hola Priyanka, ¿puedes decirme si la cláusula "y t1.name = t2.name" es necesaria aquí y por qué?
Koen
2
Esto es excelente. Estaba buscando optimizar una función UDF como se enumera en la respuesta aceptada que estaba matando a mi servidor. Pasé de una búsqueda de 102 segundos a menos de 1. La comparación del plan de ejecución fue del 78% -22%, pero eso no se relaciona con el tiempo de ejecución ...
toxaq
Solo un recordatorio de que necesitará ese 'inicial', o de lo contrario terminará con corchetes angulares en su salida.
Tim Scarborough
45

1. Cree la 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. Usar en subconsulta:

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

3. Si está utilizando un procedimiento almacenado, puede hacer lo siguiente:

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
fuente
1
Esto todavía se siente como un truco. Todavía estoy usando subconsultas, así que todavía hay mucho procesamiento adicional. Estoy seguro de que existe una solución mejor (reestructuración de la tabla u otra forma de ver el problema).
Donnie Thomas
1
Yo no llamaría a esto un truco. Es más eficiente de lo que sería un cursor y carece de la sobrecarga que sería necesaria para crear una tabla temporal con los datos estructurados de la manera que desee.
Scott Lawrence
1
Lástima que las columnas no puedan ser parámetros. ¡Tal como está, necesitará crear una función para cada relación infantil!
John Paul Jones
1
Está bien, necesito combinar solo estas columnas en particular. El resto son uniones "tradicionales".
Donnie Thomas
No recuerdo la mejor manera de hacer esto sin este método.
aF.
11

Creo que estás en el camino correcto con COALESCE. Vea aquí un ejemplo de cómo construir una cadena delimitada por comas:

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

Ben Hoffstein
fuente
2
¡Increíble! Había visto algunos enlaces sobre COALESCE, pero implicaban la creación de UDF con disparadores. El enlace que ha enviado tiene la clave, con una sola declaración SELECT. Estoy agregando una respuesta con la solución correcta, para que sea más fácil de encontrar para otros. ¡Gracias!
Donnie Thomas
1
Hola Ben, creo que la respuesta necesita un poco más de detalle, es decir, cómo crear el UDF, etc. Una vez que me dé cuenta de esto, agregaré la solución como una respuesta editable de la comunidad. Siéntase libre de editarlo, después de lo cual lo aceptaré como la respuesta. Perdón por la confusion.
Donnie Thomas
11

En MySQL hay una función group_concat que devolverá lo que está pidiendo.

SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn) 
as SomColumnGroup FROM TableA LEFT JOIN TableB ON 
TableB.TableA_ID = TableA.ID
Jacob
fuente
1
Esto habría sido perfecto, si hubiera una función similar en SQL Server. Tal como está, estoy usando la solución de Ben para armar lo que quiero.
Donnie Thomas
0

Es posible que deba proporcionar algunos detalles más para obtener una respuesta más precisa.

Dado que su conjunto de datos parece un poco estrecho, podría considerar usar una fila por resultado y realizar el posprocesamiento en el cliente.

Entonces, si realmente está buscando que el servidor haga el trabajo, devuelva un conjunto de resultados como

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

que por supuesto es un INNER JOIN en ID

Una vez que tenga el conjunto de resultados en el cliente, mantenga una variable llamada CurrentName y úsela como un disparador cuando deje de recopilar SomeColumn en lo útil que desea que haga.

Cuenta
fuente
Pensé en esto, pero no estaba muy seguro de si se trataba de una solución elegante; me gustaría que SQL Server devolviera el conjunto de resultados correctamente construido, no algo que deba procesarse más. ¿Necesitaría detalles adicionales? He simplificado la estructura de la tabla, pero creo que la tienes.
Donnie Thomas
0

Suponiendo que solo tiene cláusulas WHERE en la tabla A, cree un procedimiento almacenado así:

SELECT Id, Name From tableA WHERE ...

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

Luego llene un DataSet ds con él. Entonces

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

Finalmente puede agregar un repetidor en la página que ponga comas para cada línea

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

De esta manera lo hará del lado del cliente pero con una sola consulta, pasando datos mínimos entre la base de datos y el frontend

Sklivvz
fuente
0

Probé la solución que priyanka.sarkar mencionó y no logré que funcionara como pidió el OP. Aquí está la solución con la que terminé:

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

Solución a continuación:

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

Use esto, también puede cambiar las Uniones

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

Esto funcionará para seleccionar de una tabla diferente usando una subconsulta.

ATHAR
fuente
-1

He revisado todas las respuestas. Creo que la inserción de la base de datos debería ser como:

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

La coma debe estar al final anterior y hacer la búsqueda por like %,X,%

rsda
fuente