2009-01-16 44 views
290

Estoy tratando de migrar una aplicación basada en MySQL a Microsoft SQL Server 2005 (no por elección, pero así es la vida).Simulando la función group_concat MySQL en Microsoft SQL Server 2005?

En la aplicación original, que utiliza declaraciones que cumplen casi por completo ANSI-SQL, con una excepción importante - hemos utilizado la función de MySQL group_concat con bastante frecuencia.

group_concat, por cierto, hace esto: dada una tabla de, por ejemplo, los nombres de los empleados y proyectos ...

SELECT empName, projID FROM project_members; 

devuelve:

ANDY | A100 
ANDY | B391 
ANDY | X010 
TOM | A100 
TOM | A510 

... y aquí es lo que conseguir con GROUP_CONCAT:

SELECT 
    empName, group_concat(projID SEPARATOR '/') 
FROM 
    project_members 
GROUP BY 
    empName; 

devuelve:

ANDY | A100/B391/X010 
TOM | A100/A510 

Entonces, lo que me gustaría saber es: ¿es posible escribir, por ejemplo, una función definida por el usuario en SQL Server que emule la funcionalidad de group_concat?

que casi no tienen experiencia en el uso UDF, procedimientos almacenados, ni nada de eso, sólo hacia arriba SQL, así que por favor errar en el lado de demasiada explicación :)

+0

Un par de enlaces útiles: http://www.postgresonline.com/journal/archives/191-stringagg.html y http://consultingblogs.emc.com/jamiethomson/archive/2009/07/16/string -aggregation-in-t-sql-amp-pl-sql.aspx – bernhof

+0

posible duplicado de [¿Cómo puedo crear una lista separada por comas usando una consulta SQL?] (http://stackoverflow.com/questions/1817985/how -do-i-create-a-comma-separated-list-using-a-sql-query) - esa publicación es más amplia así que elegiría esa como canonical – TMS

+0

posible duplicación de [grupo SQL \ _concat función en SQL Server] (http://stackoverflow.com/questions/8868604/sql-group-concat-function-in-sql-server) – Trikaldarshi

Respuesta

146

ninguna manera realmente fácil de hacer esto. Hay muchas ideas por ahí, sin embargo.

Best one I've found:

SELECT table_name, LEFT(column_names , LEN(column_names)-1) AS column_names 
FROM information_schema.columns AS extern 
CROSS APPLY 
(
    SELECT column_name + ',' 
    FROM information_schema.columns AS intern 
    WHERE extern.table_name = intern.table_name 
    FOR XML PATH('') 
) pre_trimmed (column_names) 
GROUP BY table_name, column_names; 

o una versión que funciona correctamente si los datos podrían contener caracteres como <

WITH extern 
    AS (SELECT DISTINCT table_name 
     FROM INFORMATION_SCHEMA.COLUMNS) 
SELECT table_name, 
     LEFT(y.column_names, LEN(y.column_names) - 1) AS column_names 
FROM extern 
     CROSS APPLY (SELECT column_name + ',' 
        FROM INFORMATION_SCHEMA.COLUMNS AS intern 
        WHERE extern.table_name = intern.table_name 
        FOR XML PATH(''), TYPE) x (column_names) 
     CROSS APPLY (SELECT x.column_names.value('.', 'NVARCHAR(MAX)')) y(column_names) 
+0

Este ejemplo funcionó para mí, pero traté de hacer otra agregación y no funcionó, me dio un error: "el nombre de correlación 'pre_trimmed' se especifica varias veces en una cláusula FROM." – PhilChuang

+6

'pre_trimmed' es solo un alias para la subconsulta. Los alias son necesarios para las subconsultas y tienen que ser únicos, por lo que para otra subconsulta, cámbielo a algo único ... – Koen

+0

¿puede mostrar un ejemplo sin table_name como nombre de columna? Es confuso. –

6

Con el código de abajo tiene que establecer PermissionLevel = Externa sobre las propiedades del proyecto antes de implementar y cambiar la base de datos para confiar en el código externo (asegúrese de leer en otro lugar sobre riesgos de seguridad y alternativas [como certificados]) ejecutando "ALTER DATABASE database_name SET TRUSTWORTHY ON".

using System; 
using System.Collections.Generic; 
using System.Data.SqlTypes; 
using System.IO; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 
using Microsoft.SqlServer.Server; 

[Serializable] 
[SqlUserDefinedAggregate(Format.UserDefined, 
MaxByteSize=8000, 
IsInvariantToDuplicates=true, 
IsInvariantToNulls=true, 
IsInvariantToOrder=true, 
IsNullIfEmpty=true)] 
    public struct CommaDelimit : IBinarySerialize 
{ 


[Serializable] 
private class StringList : List<string> 
{ } 

private StringList List; 

public void Init() 
{ 
    this.List = new StringList(); 
} 

public void Accumulate(SqlString value) 
{ 
    if (!value.IsNull) 
    this.Add(value.Value); 
} 

private void Add(string value) 
{ 
    if (!this.List.Contains(value)) 
    this.List.Add(value); 
} 

public void Merge(CommaDelimit group) 
{ 
    foreach (string s in group.List) 
    { 
    this.Add(s); 
    } 
} 

void IBinarySerialize.Read(BinaryReader reader) 
{ 
    IFormatter formatter = new BinaryFormatter(); 
    this.List = (StringList)formatter.Deserialize(reader.BaseStream); 
} 

public SqlString Terminate() 
{ 
    if (this.List.Count == 0) 
    return SqlString.Null; 

    const string Separator = ", "; 

    this.List.Sort(); 

    return new SqlString(String.Join(Separator, this.List.ToArray())); 
} 

void IBinarySerialize.Write(BinaryWriter writer) 
{ 
    IFormatter formatter = new BinaryFormatter(); 
    formatter.Serialize(writer.BaseStream, this.List); 
} 
    } 

He probado esto mediante una consulta que se parece a:

SELECT 
dbo.CommaDelimit(X.value) [delimited] 
FROM 
(
    SELECT 'D' [value] 
    UNION ALL SELECT 'B' [value] 
    UNION ALL SELECT 'B' [value] -- intentional duplicate 
    UNION ALL SELECT 'A' [value] 
    UNION ALL SELECT 'C' [value] 
) X 

y rendimientos: A, B, C, D

43

Posiblemente demasiado tarde para ser de beneficio, pero ¿No es esta la manera más fácil de hacer las cosas?

SELECT  empName, projIDs = replace 
          ((SELECT Surname AS [data()] 
           FROM project_members 
           WHERE empName = a.empName 
           ORDER BY empName FOR xml path('')), ' ', REQUIRED SEPERATOR) 
FROM   project_members a 
WHERE  empName IS NOT NULL 
GROUP BY empName 
+0

Interesante. Ya he terminado el proyecto, pero intentaré con este método. ¡Gracias! – DanM

+6

Buen truco: el único problema es para los apellidos con espacios que reemplazarán el espacio con el separador. –

+0

Me he encontrado con un problema así, Mark. Desafortunadamente, hasta que MSSQL se adapte a los tiempos e introduzca GROUP_CONCAT, este es el método menos intensivo que pude obtener para lo que aquí se necesita. –

4

Sobre la respuesta de J Hardiman, ¿qué tal:

SELECT empName, projIDs= 
    REPLACE(
    REPLACE(
     (SELECT REPLACE(projID, ' ', '-somebody-puts-microsoft-out-of-his-misery-please-') AS [data()] FROM project_members WHERE empName=a.empName FOR XML PATH('')), 
     ' ', 
     '/'), 
    '-somebody-puts-microsoft-out-of-his-misery-please-', 
    ' ') 
    FROM project_members a WHERE empName IS NOT NULL GROUP BY empName 

Por cierto, es el uso del "Apellido" un error tipográfico o estoy sin entender un concepto aquí?

De todos modos, muchas gracias chicos primo que me salvó bastante tiempo :)

+1

Respuesta bastante poco amistosa si me lo pides y nada útil como respuesta. –

+1

solo viendo eso ahora ... No quise decirlo de una manera mala, en ese momento estaba muy frustrado con el servidor sql (todavía soy). las respuestas de esta publicación realmente fueron útiles en realidad; EDITAR: ¿por qué no fue útil por cierto? me funcionó – user422190

6

probado estos pero para mis propósitos en MS SQL Server 2005, la siguiente era más útil que he encontrado en xaprb

declare @result varchar(8000); 

set @result = ''; 

select @result = @result + name + ' ' 

from master.dbo.systypes; 

select rtrim(@result); 

@ Marcos como usted mencionó que era el carácter de espacio que causó problemas para mí.

146

Puedo llegar un poco tarde a la fiesta, pero este método funciona para mí y es más fácil que el método COALESCE.

SELECT STUFF(
      (SELECT ',' + Column_Name 
       FROM Table_Name 
       FOR XML PATH ('')) 
      , 1, 1, '') 
+0

Esto solo muestra cómo concaturar valores - group_concat los concatena por grupo, lo cual es más desafiante (y lo que el OP parece requerir). Consulte la respuesta aceptada al SO 15154644 para saber cómo hacer esto: la cláusula WHERE es la adición crítica – DJDave

6

Para concatenar todos los nombres de los gestores de proyectos de proyectos que tienen varios gestores de proyectos escribe:

SELECT a.project_id,a.project_name,Stuff((SELECT N'/ ' + first_name + ', '+last_name FROM projects_v 
where a.project_id=project_id 
FOR 
XML PATH(''),TYPE).value('text()[1]','nvarchar(max)'),1,2,N'' 
) mgr_names 
from projects_v a 
group by a.project_id,a.project_name 
27

Tener un vistazo al proyecto GROUP_CONCAT en CodePlex, creo que hace exactamente lo que está buscando :

This project contains a set of SQLCLR User-defined Aggregate functions (SQLCLR UDAs) that collectively offer similar functionality to the MySQL GROUP_CONCAT function. There are multiple functions to ensure the best performance based on the functionality required...

+5

¿Por qué el voto a favor? Por favor explique ... – MaxiWheat

+5

Lo he usado y funciona bien hasta ahora. –

+1

@MaxiWheat: muchos tipos no leen la pregunta o responden con cuidado antes de hacer clic abajo. Afecta directamente a la publicación del propietario debido a su error. –

25

SQL Server 2017 sí introduce una nueva función agregada

STRING_AGG (expression, separator).

Concatenates the values of string expressions and places separator values between them. The separator is not added at the end of string.

Los elementos concatenados se pueden pedir añadiendo WITHIN GROUP (ORDER BY some_expression)

Para versiones 2005-2016 Me suelen utilizar el método de XML en la respuesta aceptada.

Sin embargo, esto puede fallar en algunas circunstancias. p.ej. si los datos que se van concatenados contiene CHAR(29) ves

FOR XML could not serialize the data ... because it contains a character (0x001D) which is not allowed in XML.

Un método más robusto que puede hacer frente a todos los personajes sería utilizar un agregado CLR. Sin embargo, la aplicación de un orden a los elementos concatenados es más difícil con este enfoque.

El método de asignación a una variable es not guaranteed y debe evitarse en el código de producción.

+0

Esto también está disponible ahora en Azure SQL: https://azure.microsoft.com/en-us/roadmap/new-t-sql-string-functions-in-azure-sql-database/ –