Моделирование функции group_concat MySQL в Microsoft SQL Server 2005?

Я пытаюсь перенести приложение на основе MySQL на Microsoft SQL Server 2005 (не по своему выбору, а на то, что жизнь).

В исходном приложении мы использовали почти полностью совместимые с ANSI-SQL операторы, с одним существенным исключением - мы часто использовали функцию MySQL group_concat.

group_concat, кстати, делает это: учитывая таблицу, скажем, имен сотрудников и проектов...

SELECT empName, projID FROM project_members;

возвращает:

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

... и вот что вы получаете с group_concat:

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

возвращает:

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

Так что я хотел бы знать: возможно ли написать, скажем, пользовательскую функцию в SQL Server, которая эмулирует функциональность group_concat?

У меня почти нет опыта использования UDF, хранимых процедур или чего-либо подобного, просто прямого SQL, поэтому, пожалуйста, перепутайте слишком много объяснений:)

Ответ 1

Нет РЕАЛЬНОГО простого способа сделать это. Тем не менее, много идей.

Лучший, который я нашел:

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;

Или версия, которая работает правильно, если данные могут содержать символы, такие как <

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) 

Ответ 2

Возможно, я немного опоздал на вечеринку, но этот метод работает для меня и проще, чем метод COALESCE.

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

Ответ 3

Возможно, слишком поздно, чтобы принести пользу сейчас, но разве это не самый простой способ сделать что-то?

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

Ответ 4

SQL Server 2017 представляет новую агрегатную функцию

STRING_AGG ( expression, separator).

Объединение значений строковых выражений и разделителей мест между ними. Сепаратор не добавляется в конец строки.

Конкатенированные элементы можно упорядочить, добавив WITHIN GROUP (ORDER BY some_expression)

Для версий 2005-2016 Я обычно использую метод XML в принятом ответе.

В некоторых случаях это может быть неудачным. например если данные, которые будут конкатенированы, содержат CHAR(29), вы видите

FOR XML не может сериализовать данные... потому что это содержит символ (0x001D), который не разрешен в XML.

Более надежный метод, который может обрабатывать все символы, - это использовать агрегат CLR. Однако применение такого подхода к конкатенированным элементам является более сложным с этим подходом.

Метод присвоения переменной не гарантируется и его следует избегать в производственном коде.

Ответ 5

Взгляните на проект GROUP_CONCAT на Github, я думаю, что выполняю именно то, что вы ищете:

Этот проект содержит набор пользовательских функций SQLCLR, определяемых пользователем (SQLCLR UDA), которые совместно предлагают аналогичную функциональность функции MySQL GROUP_CONCAT. Существует множество функций, обеспечивающих максимальную производительность на основе требуемой функциональности...

Ответ 6

С помощью приведенного ниже кода вы должны установить PermissionLevel = External в своих свойствах проекта перед развертыванием и изменить базу данных, чтобы доверять внешнему коду (обязательно прочитайте в другом месте об угрозах безопасности и альтернативах [например, сертификатах]), выполнив "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);
 }
    }

Я тестировал это с помощью запроса, который выглядит так:

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 

И дает: A, B, C, D

Ответ 7

Пробовал эти, но для моих целей в MS SQL Server 2005 следующее было наиболее полезным, которое я нашел в xaprb

declare @result varchar(8000);

set @result = '';

select @result = @result + name + ' '

from master.dbo.systypes;

select rtrim(@result);

@Mark, как вы уже упоминали, это был пробельный символ, который вызывал проблемы для меня.

Ответ 8

Чтобы объединить все имена менеджеров проектов из проектов с несколькими менеджерами проектов, пишите:

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

Ответ 9

О J Hardiman ответ, как насчет:

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

Кстати, это использование "Фамилии" опечатки или я не понимаю понятие здесь?

В любом случае, спасибо, много парней, потому что это спасло меня довольно долго:)