Mô phỏng chức năng MySQL nhóm_concat trong Microsoft SQL Server 2005?


347

Tôi đang cố gắng di chuyển một ứng dụng dựa trên MySQL sang Microsoft SQL Server 2005 (không phải theo lựa chọn, mà là cuộc sống).

Trong ứng dụng gốc, chúng tôi đã sử dụng gần như hoàn toàn các câu lệnh tuân thủ ANSI-SQL, với một ngoại lệ quan trọng - chúng tôi đã sử dụng group_concatchức năng của MySQL khá thường xuyên.

group_concat, nhân tiện, làm điều này: đưa ra một bảng, nói, tên nhân viên và dự án ...

SELECT empName, projID FROM project_members;

trả về:

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

... Và đây là những gì bạn nhận được với group_concat:

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

trả về:

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

Vì vậy, điều tôi muốn biết là: Có thể viết, giả sử, một hàm do người dùng định nghĩa trong SQL Server mô phỏng chức năng của group_concat?

Tôi gần như không có kinh nghiệm sử dụng UDF, các thủ tục được lưu trữ, hoặc bất cứ điều gì tương tự, chỉ cần SQL ngay thẳng, vì vậy xin vui lòng lỗi ở phía quá nhiều lời giải thích :)



Đây là một câu hỏi cũ, nhưng tôi thích giải pháp CLR được đưa ra ở đây .
Diego

có thể trùng lặp Làm thế nào để tôi tạo một danh sách được phân tách bằng dấu phẩy bằng cách sử dụng truy vấn SQL? - bài đăng đó rộng hơn nên tôi sẽ chọn bài đó là hợp quy
TMS

có thể trùng lặp chức năng
nhóm SQL_concat

Làm thế nào để bạn biết danh sách theo thứ tự nên được xây dựng, ví dụ: bạn hiển thị A100 / B391 / X010 nhưng không có thứ tự ngầm trong cơ sở dữ liệu quan hệ, nó có thể dễ dàng là X010 / A100 / B391 hoặc bất kỳ kết hợp nào khác.
Steve Ford

Câu trả lời:


174

Không có cách dễ dàng THỰC SỰ để làm điều này. Có rất nhiều ý tưởng ngoài kia, mặc dù.

Tốt nhất tôi đã tìm thấy :

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;

Hoặc một phiên bản hoạt động chính xác nếu dữ liệu có thể chứa các ký tự như <

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) 

1
Ví dụ này có hiệu quả với tôi, nhưng tôi đã thử thực hiện một phép gộp khác và nó không hoạt động, đã cho tôi một lỗi: "tên tương quan 'pre_trimmed' được chỉ định nhiều lần trong mệnh đề TỪ."
PhilChuang

7
'pre_trimmed' chỉ là bí danh cho truy vấn con. Các bí danh được yêu cầu cho các truy vấn con và phải là duy nhất, vì vậy đối với một truy vấn con khác, hãy thay đổi nó thành một cái gì đó độc đáo ...
Koen

2
bạn có thể hiển thị một ví dụ mà không có tên_bảng như một tên cột không?
S.Mason

169

Tôi có thể đến bữa tiệc muộn một chút nhưng phương pháp này hiệu quả với tôi và dễ hơn phương pháp COALESCE.

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

1
Điều này chỉ cho thấy cách ghép các giá trị - group_concat kết hợp chúng theo nhóm, điều này khó khăn hơn (và những gì OP dường như yêu cầu). Xem câu trả lời được chấp nhận cho SO 15154644 để biết cách thực hiện việc này - mệnh đề WHERE là phần bổ sung quan trọng
DJDave


51

Có thể quá muộn để có lợi bây giờ, nhưng đây không phải là cách dễ nhất để làm việc sao?

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

Hấp dẫn. Tôi đã hoàn thành dự án trong tay, nhưng tôi sẽ thử phương pháp này. Cảm ơn!
DanM

7
Thủ thuật hay - chỉ có vấn đề với họ và không gian, nó sẽ thay thế không gian bằng dấu phân cách.
Đánh dấu Elliot

Tôi đã gặp phải một vấn đề như vậy bản thân mình, Mark. Thật không may, cho đến khi MSSQL hòa nhập với thời đại và giới thiệu GROUP_CONCAT, đây là phương pháp ít tốn kém nhất mà tôi có thể đưa ra cho những gì cần thiết ở đây.
J Hardiman

Cám ơn vì cái này! Đây là một Fiddle SQL cho thấy nó hoạt động: sqlfiddle.com/#!6/c5d56/3
chạy vào

42

SQL Server 2017 không giới thiệu chức năng tổng hợp mới

STRING_AGG ( expression, separator).

Nối các giá trị của biểu thức chuỗi và đặt các giá trị dấu phân cách giữa chúng. Dấu phân cách không được thêm vào cuối chuỗi.

Các yếu tố nối có thể được đặt hàng bằng cách nối thêm WITHIN GROUP (ORDER BY some_expression)

Đối với các phiên bản 2005-2016, tôi thường sử dụng phương thức XML trong câu trả lời được chấp nhận.

Điều này có thể thất bại trong một số trường hợp tuy nhiên. ví dụ: nếu dữ liệu được nối có chứa CHAR(29)bạn thấy

FOR XML không thể tuần tự hóa dữ liệu ... bởi vì nó chứa một ký tự (0x001D) không được phép trong XML.

Một phương pháp mạnh mẽ hơn có thể xử lý tất cả các ký tự sẽ là sử dụng tổng hợp CLR. Tuy nhiên, áp dụng một trật tự cho các yếu tố nối là khó khăn hơn với phương pháp này.

Phương pháp gán cho một biến không được đảm bảo và nên tránh trong mã sản xuất.


Đây cũng là sẵn ngay bây giờ trong Azure SQL: azure.microsoft.com/en-us/roadmap/...
Simon_Weaver

34

Hãy xem dự án GROUP_CONCAT trên Github, tôi nghĩ rằng tôi làm chính xác những gì bạn đang tìm kiếm:

Dự án này chứa một tập hợp các hàm Tổng hợp do người dùng định nghĩa SQLCLR (SQLCLR UDAs) cung cấp chung chức năng tương tự như chức năng MySQL GROUP_CONCAT. Có nhiều chức năng để đảm bảo hiệu suất tốt nhất dựa trên chức năng được yêu cầu ...


2
@MaxiWheat: rất nhiều chàng trai không đọc câu hỏi hoặc trả lời cẩn thận trước khi nhấp vào bỏ phiếu. Nó ảnh hưởng đến chủ sở hữu bài trực tiếp do sai lầm của họ.
Steve Lam

Công trình tuyệt vời. Tính năng duy nhất tôi thiếu là khả năng sắp xếp trên một cột mà MySQL group_concat () có thể thích:GROUP_CONCAT(klascode,'(',name,')' ORDER BY klascode ASC SEPARATOR ', ')
Ngày

10

Để ghép tất cả các tên người quản lý dự án từ các dự án có nhiều người quản lý dự án viết:

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

Với mã bên dưới, bạn phải đặt PermissionLevel = Bên ngoài trên các thuộc tính dự án của bạn trước khi bạn triển khai và thay đổi cơ sở dữ liệu để tin tưởng mã bên ngoài (hãy chắc chắn đọc các nơi khác về rủi ro bảo mật và các lựa chọn thay thế [như chứng chỉ]) bằng cách chạy "ALTER DATABASE cơ sở dữ liệu TRUSTWORTHY TRÊN ".

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);
 }
    }

Tôi đã kiểm tra điều này bằng một truy vấn giống như:

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 

Và sản lượng: A, B, C, D


9

Đã thử những thứ này nhưng với mục đích của tôi trong MS SQL Server 2005, những thứ sau đây là hữu ích nhất, mà tôi tìm thấy ở xaprb

declare @result varchar(8000);

set @result = '';

select @result = @result + name + ' '

from master.dbo.systypes;

select rtrim(@result);

@Mark như bạn đã đề cập, đó là nhân vật không gian gây ra vấn đề cho tôi.


Tôi nghĩ rằng công cụ không thực sự đảm bảo bất kỳ trật tự nào với phương thức này, bởi vì các biến được tính là luồng dữ liệu tùy thuộc vào kế hoạch thực hiện. Nó dường như làm việc hầu hết thời gian cho đến nay mặc dù.
phil_w

6

Về câu trả lời của J Hardiman, làm thế nào về:

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

Nhân tiện, việc sử dụng "Họ" là một lỗi đánh máy hay tôi không hiểu một khái niệm ở đây?

Dù sao, cảm ơn rất nhiều người vì nó đã tiết kiệm cho tôi khá nhiều thời gian :)


1
Thay vào đó là câu trả lời không thân thiện nếu bạn hỏi tôi và hoàn toàn không hữu ích như một câu trả lời.
Tim Meers

1
chỉ nhìn thấy điều đó bây giờ ... Tôi không có ý đó theo một cách có nghĩa, tại thời điểm đó tôi đã rất thất vọng với máy chủ sql (vẫn còn). câu trả lời từ bài viết này thực sự hữu ích thực sự; EDIT: tại sao nó không hữu ích btw? nó đã lừa tôi
user422190

1

Đối với các nhân viên Google của tôi ở ngoài kia, đây là một giải pháp plug-and-play rất đơn giản, hiệu quả với tôi sau khi vật lộn với các giải pháp phức tạp hơn trong một thời gian:

SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ CONVERT(VARCHAR(10), projID ) 
                     FROM returns 
                     WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM 
returns t

Lưu ý rằng tôi phải chuyển đổi ID thành VARCHAR để ghép nó thành một chuỗi. Nếu bạn không phải làm điều đó, đây là một phiên bản thậm chí đơn giản hơn:

SELECT
distinct empName,
NewColumnName=STUFF((SELECT ','+ projID
                     FROM returns 
                     WHERE empName=t.empName FOR XML PATH('')) , 1 , 1 , '' )
FROM 
returns t

Tất cả tín dụng cho việc này được chuyển đến đây: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/9508abc2-46e7-4186-b57f-7f368374e084/replicating-groupconcat-feft-of-mysql- máy chủ sql? forum = transactsql

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.