Simulieren Group_concat MySQL-Funktion in Microsoft SQL Server 2005?
-
19-08-2019 - |
Frage
Ich versuche, eine MySQL-basierte Anwendung auf dem Microsoft SQL Server 2005 zu migrieren (nicht durch Wahl, aber das ist das Leben).
In der ursprünglichen App verwenden wir fast vollständig ANSI-SQL-kompatible Aussagen, mit einer bedeutenden Ausnahme -. Wir verwenden MySQL group_concat
Funktion ziemlich häufig
group_concat
, nebenbei bemerkt, tut dies: Da eine Tabelle von, sagen wir, Mitarbeiternamen und Projekte ...
SELECT empName, projID FROM project_members;
Rückgabe:
ANDY | A100
ANDY | B391
ANDY | X010
TOM | A100
TOM | A510
... und hier ist das, was Sie mit Group_concat bekommen:
SELECT
empName, group_concat(projID SEPARATOR ' / ')
FROM
project_members
GROUP BY
empName;
Rückgabe:
ANDY | A100 / B391 / X010
TOM | A100 / A510
Also, was ich möchte wissen, ist: Ist es möglich, zu schreiben, sagen wir, eine benutzerdefinierte Funktion in SQL Server, die die Funktionalität von group_concat
emuliert?
Ich habe fast keine Erfahrung UDF, gespeicherte Prozeduren oder ähnliches verwendet wird, nur straight-up SQL, so wenden Sie sich bitte auf der Seite zu viel Erklärung irren:)
Lösung
Nein REAL einfache Möglichkeit, dies zu tun. Viele Ideen da draußen, aber.
beste, die ich gefunden habe :
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;
oder eine Version, die korrekt, wenn die Daten enthalten Zeichen wie <
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)
Andere Tipps
Ich kann ein bisschen spät, um die Partei, aber diese Methode funktioniert für mich und ist einfacher als die COALESCE-Methode.
SELECT STUFF(
(SELECT ',' + Column_Name
FROM Table_Name
FOR XML PATH (''))
, 1, 1, '')
Möglicherweise zu spät von Nutzen zu sein, aber das ist nicht der einfachste Weg, Dinge zu tun?
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 einführt eine neue Aggregatfunktion
STRING_AGG ( expression, separator)
.
Verkettet die Werte von String-Ausdrücke und Orte Separator Werte zwischen ihnen. Der Separator ist nicht am Ende der Zeichenfolge hinzugefügt.
Die verketteten Elemente können durch Anhängen WITHIN GROUP (ORDER BY some_expression)
bestellt werden
Für Versionen 2005-2016 ich in der Regel die XML-Methode in der akzeptierten Antwort verwenden.
Dies kann jedoch unter bestimmten Umständen fehlschlagen. z.B. wenn die Daten verknüpft werden soll, enthält CHAR(29)
Sie sehen
FOR XML könnten die Daten nicht serialisiert ... weil es enthält ein Zeichen (0x001D), die nicht in XML erlaubt ist.
Eine robustere Methode, die mit allen Charakteren umgehen können wäre ein CLR Aggregat zu verwenden. eine Ordnung der verketteten Elemente jedoch Anwendung ist schwieriger, mit diesem Ansatz.
Das Verfahren einer Variablen zuzuweisen ist nicht garantiert und im Produktionscode vermieden werden sollte.
Haben Sie einen Blick auf die GROUP_CONCAT Projekt Github, ich glaube, ich tue genau das, was Sie suchen:
Dieses Projekt enthält eine Reihe von SQLCLR Benutzerdefinierte Aggregatfunktionen (SQLCLR UDAs), die zusammen eine ähnliche Funktionalität wie der MySQL GROUP_CONCAT Funktion bieten. Es gibt mehrere Funktionen, um die beste Leistung zu gewährleisten basiert auf der Funktionalität erforderlich ...
Um alle Projektmanager Namen von Projekten verketten, die mehrere Projektmanager haben schreiben:
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
Mit dem folgenden Code, den Sie haben PermissionLevel = External auf Ihrer Projekteigenschaft festlegen, bevor Sie bereitstellen und die Datenbank ändern externen Code zu vertrauen (sicher sein, an anderer Stelle über Sicherheitsrisiken und Alternativen zu lesen [wie Zertifikate]) durch „ALTER laufen 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);
}
}
Ich habe dies mit einer Abfrage getestet, die wie folgt aussieht:
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
Und Ausbeuten: A, B, C, D
versucht, diese aber für meine Zwecke in MS SQL Server 2005 die folgende war am nützlichsten, die ich unter xaprb
declare @result varchar(8000);
set @result = '';
select @result = @result + name + ' '
from master.dbo.systypes;
select rtrim(@result);
@ Mark wie Sie bereits erwähnt, es war der Raum Charakter, der für mich verursacht Probleme.
Über J Hardiman Antwort, wie etwa:
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
By the way, ist die Verwendung von „Name“ einen Tippfehler oder bin ich kein Konzept hier zu verstehen?
Wie auch immer, vielen Dank Jungs Cuz es hat mich gerettet einige Zeit:)
Für meine Kolleginnen und Googler da draußen, hier ist eine sehr einfache Plug-and-Play-Lösung, die für mich gearbeitet, nachdem eine Zeit lang mit den komplexeren Lösungen zu kämpfen:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID )
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Beachten Sie, dass ich die ID in eine VARCHAR, um zu konvertieren hatte es als String zu verketten. Wenn Sie das nicht tun müssen, ist hier eine noch einfachere Version:
SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
FROM returns
WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM
returns t
Alle Verdienstes hierfür liegt hier: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-function-of- mysql-in-sQL-Server? forum = TRANSACTSQL