J'essaie de migrer une application basée sur MySQL vers Microsoft SQL Server 2005 (non par choix, mais c'est la vie).
Dans l'application d'origine, nous utilisions presque entièrement des instructions conformes à ANSI-SQL, à une exception près - nous utilisions assez souvent la fonction group_concat
de MySQL.
group_concat
à propos, fait ceci: à partir d'un tableau avec, disons, les noms des employés et les projets ...
SELECT empName, projID FROM project_members;
résultats:
ANDY | A100
ANDY | B391
ANDY | X010
TOM | A100
TOM | A510
... et voici ce que vous obtenez avec group_concat:
SELECT
empName, group_concat(projID SEPARATOR ' / ')
FROM
project_members
GROUP BY
empName;
résultats:
ANDY | A100 / B391 / X010
TOM | A100 / A510
Ce que j'aimerais savoir, c'est: Est-il possible d'écrire une fonction définie par l'utilisateur dans SQL Server qui émule la fonctionnalité de group_concat
?
Je n'ai presque aucune expérience de l'utilisation de fonctions définies par l'utilisateur, de procédures stockées ou de tout autre chose du genre, juste du SQL direct, alors veuillez vous tromper du côté des explications excessives :)
Pas de VRAI moyen facile de le faire. Beaucoup d'idées, cependant.
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;
Ou une version qui fonctionne correctement si les données peuvent contenir des caractères tels que <
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)
Je suis peut-être un peu en retard à la fête, mais cette méthode fonctionne pour moi et est plus simple que la méthode COALESCE.
SELECT STUFF(
(SELECT ',' + Column_Name
FROM Table_Name
FOR XML PATH (''))
, 1, 1, '')
Peut-être trop tard pour en tirer profit maintenant, mais n'est-ce pas la meilleure façon de faire les choses?
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
SQL Server 2017 introduit une nouvelle fonction d'agrégat
STRING_AGG ( expression, separator)
.
Concatène les valeurs des expressions de chaîne et place des valeurs de séparateur entre elles. Le séparateur n'est pas ajouté à la fin de la chaîne.
Les éléments concaténés peuvent être commandés en ajoutant WITHIN GROUP (ORDER BY some_expression)
Pour les versions 2005-2016 , j'utilise généralement la méthode XML dans la réponse acceptée.
Cela peut toutefois échouer dans certaines circonstances. par exemple. si les données à concaténer contiennent CHAR(29)
, vous voyez
FOR XML n'a pas pu sérialiser les données ... car elles contiennent un caractère (0x001D) qui n'est pas autorisé en XML.
Une méthode plus robuste pouvant traiter tous les caractères consisterait à utiliser un agrégat CLR. Cependant, appliquer une commande aux éléments concaténés est plus difficile avec cette approche.
La méthode d'attribution d'une variable est non garantie et doit être évitée dans le code de production.
Regardez le projet GROUP_CONCAT sur Github, je pense que je fais exactement ce que vous recherchez:
Ce projet contient un ensemble de fonctions d'agrégation SQLCLR définies par l'utilisateur (UQ SQLCLR) qui offrent collectivement des fonctionnalités similaires à la fonction MySQL GROUP_CONCAT. Il existe plusieurs fonctions pour assurer les meilleures performances en fonction de la fonctionnalité requise ...
Pour concaténer tous les noms de gestionnaires de projets de projets ayant plusieurs gestionnaires de projets, écrivez:
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
Avec le code ci-dessous, vous devez définir PermissionLevel = External sur les propriétés de votre projet avant de déployer et modifier la base de données pour faire confiance au code externe (assurez-vous de lire ailleurs sur les risques de sécurité et les alternatives [comme les certificats]) en exécutant "ALTER DATABASE nom_base_données 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);
}
}
J'ai testé cela en utilisant une requête qui ressemble à:
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
Et donne: A, B, C, D
Essayé ces mais pour mes besoins dans MS SQL Server 2005 ce qui suit était le plus utile, que j'ai trouvé à xaprb
declare @result varchar(8000);
set @result = '';
select @result = @result + name + ' '
from master.dbo.systypes;
select rtrim(@result);
@ Mark, comme vous l'avez mentionné, c'est le caractère d'espace qui m'a causé des problèmes.
A propos de la réponse de J Hardiman, que diriez-vous:
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
À propos, est-ce que l'utilisation de "Nom de famille" est une faute de frappe ou est-ce que je ne comprends pas un concept ici?
Quoi qu'il en soit, merci beaucoup les gars parce que ça m'a fait gagner du temps :)
Pour mes collègues Googlers, voici une solution plug-and-play très simple qui a fonctionné pour moi après avoir lutté pendant un certain temps avec les solutions plus complexes:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID )
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Notez que je devais convertir l'ID en VARCHAR afin de le concaténer en tant que chaîne. Si vous n'avez pas à le faire, voici une version encore plus simple:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Tous les crédits pour cela vont ici: https://social.msdn.Microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of- mysql-in-sql-server? forum = transactsql