Làm cách nào để ghép văn bản từ nhiều hàng thành một chuỗi văn bản trong máy chủ SQL?


1913

Hãy xem xét một bảng cơ sở dữ liệu chứa các tên, với ba hàng:

Peter
Paul
Mary

Có một cách dễ dàng để biến điều này thành một chuỗi duy nhất Peter, Paul, Mary?


26
Để biết câu trả lời cụ thể cho SQL Server, hãy thử câu hỏi này .
Matt Hamilton

17
Đối với MySQL, hãy xem Group_Concat từ câu trả lời này
Pykler

26
Tôi mong muốn phiên bản tiếp theo của SQL Server sẽ cung cấp một tính năng mới để giải quyết sự kết hợp chuỗi nhiều hàng một cách thanh lịch mà không có sự ngu ngốc của FOR XML PATH.
Pete Alvin

4
Không phải SQL, nhưng nếu đây là một lần duy nhất, bạn có thể dán danh sách vào công cụ trong trình duyệt này convert.town/column-to-comma-separated-list
Stack Man

3
Trong Oracle, bạn có thể sử dụng LISTAGG (COLUMN_NAME) từ 11g r2 trước đó có một chức năng không được hỗ trợ có tên WM_CONCAT (COLUMN_NAME), hoạt động tương tự.
Richard

Câu trả lời:


1432

Nếu bạn đang sử dụng SQL Server 2017 hoặc Azure, hãy xem câu trả lời của Mathieu Renda .

Tôi đã có một vấn đề tương tự khi tôi đang cố gắng tham gia hai bảng với các mối quan hệ một-nhiều. Trong SQL 2005 tôi thấy rằng XML PATHphương thức đó có thể xử lý việc ghép các hàng rất dễ dàng.

Nếu có một bảng gọi là STUDENTS

SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

Kết quả tôi mong đợi là:

SubjectID       StudentName
----------      -------------
1               Mary, John, Sam
2               Alaina, Edward

Tôi đã sử dụng như sau T-SQL:

SELECT Main.SubjectID,
       LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
    (
        SELECT DISTINCT ST2.SubjectID, 
            (
                SELECT ST1.StudentName + ',' AS [text()]
                FROM dbo.Students ST1
                WHERE ST1.SubjectID = ST2.SubjectID
                ORDER BY ST1.SubjectID
                FOR XML PATH ('')
            ) [Students]
        FROM dbo.Students ST2
    ) [Main]

Bạn có thể làm điều tương tự theo cách gọn nhẹ hơn nếu bạn có thể ghép dấu phẩy ngay từ đầu và sử dụng substringđể bỏ qua bước đầu tiên để bạn không cần thực hiện truy vấn phụ:

SELECT DISTINCT ST2.SubjectID, 
    SUBSTRING(
        (
            SELECT ','+ST1.StudentName  AS [text()]
            FROM dbo.Students ST1
            WHERE ST1.SubjectID = ST2.SubjectID
            ORDER BY ST1.SubjectID
            FOR XML PATH ('')
        ), 2, 1000) [Students]
FROM dbo.Students ST2

13
Giải pháp tuyệt vời. Những điều sau đây có thể hữu ích nếu bạn cần xử lý các ký tự đặc biệt như các ký tự trong HTML: Rob Farley: Xử lý các ký tự đặc biệt bằng FOR XML PATH ('') .

10
Rõ ràng điều này không hoạt động nếu tên chứa các ký tự XML như <hoặc &. Xem bình luận của @ BenHinman.
Sam

23
NB: Phương pháp này phụ thuộc vào hành vi không có giấy tờ của FOR XML PATH (''). Điều đó có nghĩa là nó không nên được coi là đáng tin cậy vì bất kỳ bản vá hoặc cập nhật nào có thể thay đổi cách thức hoạt động của chức năng này. Về cơ bản, nó dựa vào một tính năng không dùng nữa.
Bacon Bits

26
@Whelkaholism Điểm mấu chốt là FOR XMLnhằm tạo ra XML, không nối các chuỗi tùy ý. Đó là lý do tại sao nó thoát &, <>để mã thực thể XML ( &amp;, &lt;, &gt;). Tôi giả sử nó cũng sẽ thoát "'đến &quot;&apos;trong các thuộc tính. Nó không GROUP_CONCAT() , string_agg(), array_agg(), listagg(), vv thậm chí nếu bạn có thể loại làm cho nó làm điều đó. Chúng ta nên dành thời gian yêu cầu Microsoft thực hiện một chức năng phù hợp.
Bacon Bits

13
Tin tốt: MS SQL Server sẽ được thêm string_aggvào v.Next. và tất cả những điều này có thể biến mất
Jason C

1009

Câu trả lời này có thể trả về kết quả không mong muốn Để có kết quả nhất quán, hãy sử dụng một trong các phương pháp FOR XML PATH chi tiết trong các câu trả lời khác.

Sử dụng COALESCE:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name 
FROM People

Chỉ cần một số giải thích (vì câu trả lời này dường như có được lượt xem tương đối thường xuyên):

  • Coalesce thực sự chỉ là một mánh gian lận hữu ích hoàn thành hai điều:

1) Không cần khởi tạo @Namesvới giá trị chuỗi trống.

2) Không cần phải loại bỏ một dải phân cách phụ ở cuối.

  • Giải pháp ở trên sẽ cho kết quả không chính xác nếu một hàng có giá trị Tên NULL (nếu có NULL , NULL sẽ tạo @Names NULL sau hàng đó và hàng tiếp theo sẽ bắt đầu lại dưới dạng một chuỗi trống. Dễ dàng cố định với một trong hai các giải pháp:
DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL

hoặc là:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + 
    ISNULL(Name, 'N/A')
FROM People

Tùy thuộc vào hành vi bạn muốn (tùy chọn đầu tiên chỉ lọc ra NULL , tùy chọn thứ hai giữ chúng trong danh sách bằng thông báo đánh dấu [thay thế 'Không áp dụng' bằng bất cứ điều gì phù hợp với bạn]).


72
Để rõ ràng, sự kết hợp không liên quan gì đến việc tạo danh sách, nó chỉ đảm bảo rằng các giá trị NULL không được bao gồm.
Graeme Perrow

17
@Graeme Perrow Không loại trừ các giá trị NULL (cần phải có WHERE cho điều đó - điều này sẽ mất kết quả nếu một trong các giá trị đầu vào là NULL) và được yêu cầu trong phương pháp này vì: NULL + không phải NULL -> NULL và không phải NULL + NULL -> NULL; còn @Name là NULL theo mặc định và trên thực tế, thuộc tính đó được sử dụng như một trọng tâm ẩn ở đây để xác định xem có nên thêm một ',' hay không.

62
Xin lưu ý rằng phương pháp nối này phụ thuộc vào SQL Server thực hiện truy vấn với một kế hoạch cụ thể. Tôi đã bị bắt gặp khi sử dụng phương pháp này (với việc bổ sung ĐẶT HÀNG B) NG). Khi nó xử lý một số lượng nhỏ hàng, nó hoạt động tốt nhưng với nhiều dữ liệu hơn, SQL Server đã chọn một kế hoạch khác dẫn đến việc chọn mục đầu tiên không có kết nối nào. Xem bài viết này của Anith Sen.
fbarber

17
Phương thức này không thể được sử dụng như một truy vấn phụ trong danh sách chọn hoặc mệnh đề where, vì nó sử dụng biến tQuery. Trong những trường hợp như vậy, bạn có thể sử dụng các phương thức được cung cấp bởi @Ritesh
R. Schreurs

11
Đây không phải là một phương pháp nối đáng tin cậy. Nó không được hỗ trợ và không nên được sử dụng (theo Microsoft, ví dụ: support.microsoft.com/en-us/kb/287515 , connect.microsoft.com/QueryServer/Feedback/Details/704389 ). Nó có thể thay đổi mà không cần cảnh báo. Sử dụng kỹ thuật XML PATH được thảo luận trong stackoverflow.com/questions/5031204/ từ Tôi đã viết thêm tại đây: marc.durdin.net/2015/07/ Kẻ
Marc Durdin

454

Máy chủ SQL 2017+ và SQL Azure: STRING_AGG

Bắt đầu với phiên bản tiếp theo của SQL Server, cuối cùng chúng ta có thể ghép nối giữa các hàng mà không cần phải dùng đến bất kỳ biến số hoặc phù thủy XML nào.

STRING_AGG (Giao dịch-SQL)

Không nhóm

SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;

Với nhóm:

SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;

Với việc phân nhóm và phân loại

SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department 
GROUP BY GroupName;

2
Và, không giống như các giải pháp CLR, bạn có quyền kiểm soát việc sắp xếp.
canon

Dường như có giới hạn hiển thị 4000 ký tự trên STRING_AGG
InspiredBy

Có cách nào để sắp xếp trong trường hợp không có NHÓM THEO (vì vậy đối với ví dụ "Không nhóm") không?
RuudvK

Cập nhật: Tôi đã quản lý để làm như sau, nhưng có cách nào sạch hơn không? CHỌN STRING_AGG (Tên, ',') NHƯ các phòng ban TỪ (CHỌN 100000 tên hàng đầu TỪ nguồn nhân lực. Đơn đặt hàng theo tên) D;
RuudvK

362

Một phương thức chưa được hiển thị thông qua XML data()lệnh trong MS SQL Server là:

Giả sử bảng được gọi là NameList với một cột được gọi là FName,

SELECT FName + ', ' AS 'data()' 
FROM NameList 
FOR XML PATH('')

trả về:

"Peter, Paul, Mary, "

Chỉ có dấu phẩy thừa phải được xử lý.

Chỉnh sửa: Như được thông qua từ nhận xét của @ NRzingh, bạn có thể sử dụng phương pháp sau để xóa dấu phẩy. Giả sử cùng tên bảng và cột:

STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands

15
thánh s ** t đó là tuyệt vời! Khi tự thực hiện, như trong ví dụ của bạn, kết quả được định dạng dưới dạng siêu liên kết, khi được nhấp (trong SSMS) sẽ mở một cửa sổ mới chứa dữ liệu, nhưng khi được sử dụng như một phần của truy vấn lớn hơn, nó chỉ xuất hiện dưới dạng một chuỗi. Có phải là một chuỗi? hoặc là xml mà tôi cần phải xử lý khác nhau trong ứng dụng sẽ sử dụng dữ liệu này?
Ben

10
Cách tiếp cận này cũng thoát các ký tự XML như <và>. Vì vậy, CHỌN '<b>' + FName + '</ b>' cho kết quả là "& lt; b & gt; John & lt; / b & gt; & lt; b & gt; Paul ..."
Lukáš Lánský

8
Giải pháp gọn gàng. Tôi nhận thấy rằng ngay cả khi tôi không thêm + ', 'nó, nó vẫn thêm một khoảng trống giữa mỗi phần tử được nối.
Baodad

8
@Baodad Đó dường như là một phần của thỏa thuận. Bạn có thể giải quyết bằng cách thay thế trên một ký tự mã thông báo đã thêm. Ví dụ: đây là một danh sách được phân cách bằng dấu phẩy hoàn hảo cho bất kỳ độ dài nào:SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
NRzingh

1
Wow, thực sự trong thử nghiệm của tôi bằng cách sử dụng dữ liệu () và thay thế là CÁCH hiệu quả hơn không. Siêu lạ.
NRzingh

306

Trong SQL Server 2005

SELECT Stuff(
  (SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
  .value('text()[1]','nvarchar(max)'),1,2,N'')

Trong SQL Server 2016

bạn có thể sử dụng cú pháp FOR JSON

I E

SELECT per.ID,
Emails = JSON_VALUE(
   REPLACE(
     (SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
    ,'"},{"_":"',', '),'$[0]._'
) 
FROM Person per

Và kết quả sẽ trở thành

Id  Emails
1   abc@gmail.com
2   NULL
3   def@gmail.com, xyz@gmail.com

Điều này sẽ hoạt động ngay cả dữ liệu của bạn chứa các ký tự XML không hợp lệ

những '"},{"_":"'là an toàn bởi vì nếu bạn dữ liệu chứa '"},{"_":"',nó sẽ được thoát để"},{\"_\":\"

Bạn có thể thay thế ', 'bằng bất kỳ dấu tách chuỗi nào


Và trong SQL Server 2017, cơ sở dữ liệu Azure SQL

Bạn có thể sử dụng chức năng STRING_AGG mới


3
Sử dụng tốt chức năng STUFF để kết hợp hai ký tự đầu.
David

3
Tôi thích giải pháp này nhất vì tôi có thể dễ dàng sử dụng nó trong danh sách chọn bằng cách thêm 'dưới dạng <nhãn>'. Tôi không chắc chắn làm thế nào để làm điều này với giải pháp của @Ritesh.
R. Schreurs

13
Đây là tốt hơn so với các câu trả lời được chấp nhận bởi vì tùy chọn này cũng xử lý nhân vật reserverd XML un-thoát như <, >, &, vv mà FOR XML PATH('')sẽ tự động thoát ra.
BateTech

Đây là một phản hồi tuyệt vời vì nó đã giải quyết được vấn đề và cung cấp những cách tốt nhất để thực hiện mọi thứ trong các phiên bản SQL khác nhau. Tôi ước tôi có thể sử dụng 2017 / Azure
Chris Ward

120

Trong MySQL có một hàm, GROUP_CONCAT () , cho phép bạn nối các giá trị từ nhiều hàng. Thí dụ:

SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people 
FROM users 
WHERE id IN (1,2,3) 
GROUP BY a

hoạt động tốt Nhưng khi tôi sử dụng, SEPARATOR '", "'tôi sẽ bỏ lỡ một số ký tự ở cuối mục cuối cùng. Tại sao điều này có thể xảy ra?
gooleem

@gooleem Tôi không rõ ý của bạn là gì, nhưng chức năng này chỉ đặt dấu phân cách giữa các mục, không phải sau. Nếu đó không phải là câu trả lời, tôi khuyên bạn nên đăng một câu hỏi mới.
Darryl Hein

@DarrylHein cho nhu cầu của tôi, tôi đã sử dụng dấu phân cách như trên. Nhưng điều này cắt cho tôi một số ký tự ở cuối đầu ra. Điều này rất lạ và dường như là một lỗi. Tôi không có giải pháp, tôi chỉ giải quyết.
gooleem

Hoạt động cơ bản. Hai điều cần xem xét: 1) nếu cột của bạn không phải là a CHAR, bạn cần truyền nó, ví dụ qua GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')2) nếu bạn có nhiều giá trị sắp tới, bạn nên tăng group_concat_max_lennhư được viết trong stackoverflow.com/a/1278210/1498405
hardmooth

58

Sử dụng COALESCE - Tìm hiểu thêm từ đây

Ví dụ:

102

103

104

Sau đó viết mã dưới đây vào máy chủ sql,

Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers 
SELECT  @Numbers = COALESCE(@Numbers + ',', '') + Number
FROM   TableName where Number IS NOT NULL

SELECT @Numbers

Đầu ra sẽ là:

102,103,104

2
Đây thực sự là giải pháp tốt nhất IMO vì nó tránh được các vấn đề mã hóa mà FOR XML trình bày. Tôi đã sử dụng Declare @Numbers AS Nvarchar(MAX)và nó hoạt động tốt. Bạn có thể giải thích lý do tại sao bạn khuyên bạn không nên sử dụng nó?
EvilDr

7
Giải pháp này đã được đăng 8 năm trước! stackoverflow.com/a/194887/986862
Andre Figueiredo

Tại sao truy vấn này trả về ??? biểu tượng thay vì Cyrillic? Đây chỉ là vấn đề đầu ra?
Akmal Salikhov

48

Mảng postgres là tuyệt vời. Thí dụ:

Tạo một số dữ liệu thử nghiệm:

postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE                                      
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');                                                          
INSERT 0 3
test=# select * from names;
 name  
-------
 Peter
 Paul
 Mary
(3 rows)

Tổng hợp chúng trong một mảng:

test=# select array_agg(name) from names;
 array_agg     
------------------- 
 {Peter,Paul,Mary}
(1 row)

Chuyển đổi mảng thành một chuỗi được phân cách bằng dấu phẩy:

test=# select array_to_string(array_agg(name), ', ') from names;
 array_to_string
-------------------
 Peter, Paul, Mary
(1 row)

LÀM XONG

Kể từ PostgreSQL 9.0, nó thậm chí còn dễ dàng hơn .


Nếu bạn cần nhiều hơn một cột, ví dụ: id nhân viên của họ trong ngoặc sử dụng toán tử concat: select array_to_string(array_agg(name||'('||id||')'
Richard Fox

Không áp dụng cho máy chủ sql , chỉ áp dụng cho mysql
GoldBishop

46

Oracle 11g Release 2 hỗ trợ chức năng LISTAGG. Tài liệu ở đây .

COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

Cảnh báo

Hãy cẩn thận thực hiện chức năng này nếu có khả năng chuỗi kết quả sẽ vượt quá 4000 ký tự. Nó sẽ ném một ngoại lệ. Nếu đó là trường hợp thì bạn cần phải xử lý ngoại lệ hoặc cuộn chức năng của chính bạn để ngăn chuỗi tham gia vượt quá 4000 ký tự.


1
Đối với các phiên bản cũ hơn của Oracle, wm_concat là hoàn hảo. Việc sử dụng nó được giải thích trong quà tặng liên kết của Alex. Thnks Alex!
toscanelli

LISTAGGhoạt động hoàn hảo! Chỉ cần đọc tài liệu liên kết ở đây. wm_concatxóa từ phiên bản 12c trở đi.
vào

34

Trong SQL Server 2005 trở lên, hãy sử dụng truy vấn bên dưới để nối các hàng.

DECLARE @t table
(
    Id int,
    Name varchar(10)
)
INSERT INTO @t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d' 

SELECT ID,
stuff(
(
    SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('')
),1,1,'') 
FROM (SELECT DISTINCT ID FROM @t ) t

2
Tôi tin rằng điều này không thành công khi các giá trị chứa các ký hiệu XML như <hoặc &.
Sam

28

Tôi không có quyền truy cập vào Máy chủ SQL tại nhà, vì vậy tôi đoán cú pháp ở đây, nhưng nó ít nhiều:

DECLARE @names VARCHAR(500)

SELECT @names = @names + ' ' + Name
FROM Names

11
Bạn cần phải khởi tạo @names thành một cái gì đó không null, nếu không bạn sẽ nhận được NULL xuyên suốt; bạn cũng cần xử lý dấu phân cách (bao gồm cả phần không cần thiết)
Marc Gravell

3
vấn đề duy nhất với phương pháp này (mà tôi sử dụng mọi lúc) là bạn không thể nhúng nó
ekkis

1
Để thoát khỏi không gian hàng đầu, hãy thay đổi truy vấn thànhSELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
Tian van Heerden

Ngoài ra, bạn phải kiểm tra xem Tên không phải là null, bạn có thể thực hiện bằng cách thực hiện:SELECT @names = @names + ISNULL(' ' + Name, '')
Vita1ij 18/03/2016

28

Một giải pháp CTE đệ quy đã được đề xuất, nhưng không có mã nào được cung cấp. Mã dưới đây là một ví dụ về CTE đệ quy. Lưu ý rằng mặc dù kết quả khớp với câu hỏi, dữ liệu không hoàn toàn khớp với mô tả đã cho, vì tôi cho rằng bạn thực sự muốn thực hiện điều này trên các nhóm hàng, không phải tất cả các hàng trong bảng. Thay đổi nó để phù hợp với tất cả các hàng trong bảng được để lại như một bài tập cho người đọc.

;WITH basetable AS (
    SELECT
        id,
        CAST(name AS VARCHAR(MAX)) name, 
        ROW_NUMBER() OVER (Partition BY id ORDER BY seq) rw, 
        COUNT(*) OVER (Partition BY id) recs 
    FROM (VALUES
        (1, 'Johnny', 1),
        (1, 'M', 2), 
        (2, 'Bill', 1),
        (2, 'S.', 4),
        (2, 'Preston', 5),
        (2, 'Esq.', 6),
        (3, 'Ted', 1),
        (3, 'Theodore', 2),
        (3, 'Logan', 3),
        (4, 'Peter', 1),
        (4, 'Paul', 2),
        (4, 'Mary', 3)
    ) g (id, name, seq)
),
rCTE AS (
    SELECT recs, id, name, rw
    FROM basetable
    WHERE rw = 1

    UNION ALL

    SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw + 1
    FROM basetable b
    INNER JOIN rCTE r ON b.id = r.id AND b.rw = r.rw + 1
)
SELECT name
FROM rCTE
WHERE recs = rw AND ID=4

1
Đối với sửng sốt: chèn truy vấn này 12 hàng (3 cột) vào một basetable tạm thời, sau đó tạo ra một đệ quy Common Table Expression (rCTE) và sau đó flattens name cột vào một chuỗi bằng dấu phẩy cho 4 nhóm của ids. Thoạt nhìn, tôi nghĩ rằng đây là công việc nhiều hơn những gì hầu hết các giải pháp khác cho SQL Server làm.
knb

2
@knb: không chắc đó là lời khen ngợi, lên án hay chỉ là bất ngờ. Bảng cơ sở là vì tôi thích các ví dụ của tôi thực sự hoạt động, nó thực sự không liên quan gì đến câu hỏi.
jmoreno

26

Bắt đầu với PostgreSQL 9.0, điều này khá đơn giản:

select string_agg(name, ',') 
from names;

Trong các phiên bản trước 9.0 array_agg()có thể được sử dụng như hiển thị bởi hgmnz


Để thực hiện việc này với các cột không thuộc loại văn bản, bạn cần thêm một kiểu phân loại:SELECT string_agg(non_text_type::text, ',') FROM table
Torben Kohlmeier

@TorbenKohlmeier: bạn chỉ cần điều đó cho các cột không có ký tự (ví dụ: số nguyên, số thập phân). Nó chỉ hoạt động tốt cho varcharhoặcchar
a_horse_with_no_name

26

Bạn cần tạo một biến sẽ giữ kết quả cuối cùng của bạn và chọn vào đó, như vậy.

Giải pháp dễ nhất

DECLARE @char VARCHAR(MAX);

SELECT @char = COALESCE(@char + ', ' + [column], [column]) 
FROM [table];

PRINT @char;


18

Sử dụng XML đã giúp tôi trong việc phân tách các hàng bằng dấu phẩy. Để có thêm dấu phẩy, chúng ta có thể sử dụng chức năng thay thế của SQL Server. Thay vì thêm dấu phẩy, sử dụng AS 'data ()' sẽ nối các hàng với khoảng trắng, sau này có thể được thay thế bằng dấu phẩy như cú pháp được viết dưới đây.

REPLACE(
        (select FName AS 'data()'  from NameList  for xml path(''))
         , ' ', ', ') 

2
Đây là câu trả lời tốt nhất ở đây trong tôi. Việc sử dụng biến khai báo là không tốt khi bạn cần tham gia vào một bảng khác, và điều này là tốt và ngắn. Làm tốt lắm.
David Roussel

7
điều đó không hoạt động tốt nếu dữ liệu FName đã có khoảng trắng, ví dụ "Tên tôi"
binball

Thực sự nó đang hoạt động với tôi trên ms-sql 2016 Chọn REPLACE ((chọn Tên AS 'data ()' từ Brand Where Id IN (1,2,3,4) cho đường dẫn xml ('')), '', ' , ') như tất cả các thương hiệu
Rejwanul Reja

17

Một giải pháp sẵn sàng sử dụng, không có dấu phẩy thừa:

select substring(
        (select ', '+Name AS 'data()' from Names for xml path(''))
       ,3, 255) as "MyList"

Một danh sách trống sẽ dẫn đến giá trị NULL. Thông thường, bạn sẽ chèn danh sách vào cột trong bảng hoặc biến chương trình: điều chỉnh độ dài tối đa 255 theo nhu cầu của bạn.

(Diwakar và Jens Frandsen cung cấp câu trả lời tốt, nhưng cần cải thiện.)


Có một khoảng
trắng

1
Chỉ cần thay thế ', 'bằng ','nếu bạn không muốn có thêm không gian.
Daniel Reis

13
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')

Đây là một mẫu:

DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary

10
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)

Điều này đặt dấu phẩy đi lạc ở đầu.

Tuy nhiên, nếu bạn cần các cột khác hoặc để CSV một bảng con, bạn cần bọc nó trong trường do người dùng xác định vô hướng (UDF).

Bạn cũng có thể sử dụng đường dẫn XML làm truy vấn con tương quan trong mệnh đề SELECT (nhưng tôi phải đợi cho đến khi tôi đi làm lại vì Google không làm công việc tại nhà :-)


10

Với các câu trả lời khác, người đọc câu trả lời phải biết về một bảng miền cụ thể như xe cộ hoặc học sinh. Bảng phải được tạo và điền dữ liệu để kiểm tra giải pháp.

Dưới đây là một ví dụ sử dụng bảng "Information_Schema.Columns" của SQL Server. Bằng cách sử dụng giải pháp này, không có bảng nào cần được tạo hoặc thêm dữ liệu. Ví dụ này tạo một danh sách tên cột được phân tách bằng dấu phẩy cho tất cả các bảng trong cơ sở dữ liệu.

SELECT
    Table_Name
    ,STUFF((
        SELECT ',' + Column_Name
        FROM INFORMATION_SCHEMA.Columns Columns
        WHERE Tables.Table_Name = Columns.Table_Name
        ORDER BY Column_Name
        FOR XML PATH ('')), 1, 1, ''
    )Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME 

7

Đối với Oracle DB, xem câu hỏi này: Làm thế nào nhiều hàng có thể được nối vào một trong Oracle mà không tạo ra một thủ tục được lưu trữ?

Câu trả lời tốt nhất dường như là của @Emmanuel, sử dụng hàm LISTAGG () tích hợp, có sẵn trong Oracle 11g Phiên bản 2 trở lên.

SELECT question_id,
   LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id

như @ user762952 đã chỉ ra, và theo tài liệu của Oracle http://www.oracle-base.com/articles/misc/opes-aggregation-techniques.php , hàm WM_CONCAT () cũng là một tùy chọn. Nó có vẻ ổn định, nhưng Oracle rõ ràng khuyên bạn không nên sử dụng nó cho bất kỳ SQL ứng dụng nào, vì vậy hãy tự chịu rủi ro khi sử dụng.

Ngoài ra, bạn sẽ phải viết chức năng của riêng bạn; tài liệu Oracle ở trên có một hướng dẫn về cách làm điều đó.


7

Để tránh giá trị null, bạn có thể sử dụng CONCAT ()

DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name) 
FROM Names
select @names

Thật tuyệt khi biết lý do tại sao CONCAT hoạt động. Một liên kết đến MSDN sẽ tốt đẹp.
Kỹ sư đảo ngược

7

Tôi thực sự thích sự tao nhã trong câu trả lời của Dana . Chỉ muốn làm cho nó hoàn thành.

DECLARE @names VARCHAR(MAX)
SET @names = ''

SELECT @names = @names + ', ' + Name FROM Names 

-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)

Nếu bạn đang xóa hai ký hiệu cuối ',', thì bạn cần thêm ',' sau Tên ('SELECT \ @names = \ @names + Tên +', 'TỪ Tên'). Bằng cách đó, hai ký tự cuối cùng sẽ luôn là ','.
JT_

Trong trường hợp của tôi, tôi cần phải thoát khỏi dấu phẩy hàng đầu để thay đổi truy vấn thành SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Namessau đó bạn không phải cắt bớt nó sau đó.
Tian van Heerden

6

Câu trả lời này sẽ yêu cầu một số đặc quyền trong máy chủ để làm việc.

Lắp ráp là một lựa chọn tốt cho bạn. Có rất nhiều trang web giải thích làm thế nào để tạo ra nó. Người tôi nghĩ là giải thích rất tốt là này một

Nếu bạn muốn, tôi đã tạo ra assembly, và có thể tải xuống DLL ở đây .

Khi bạn đã tải xuống, bạn sẽ cần chạy đoạn mã sau trong Máy chủ SQL của mình:

CREATE Assembly concat_assembly 
   AUTHORIZATION dbo 
   FROM '<PATH TO Concat.dll IN SERVER>' 
   WITH PERMISSION_SET = SAFE; 
GO 

CREATE AGGREGATE dbo.concat ( 

    @Value NVARCHAR(MAX) 
  , @Delimiter NVARCHAR(4000) 

) RETURNS NVARCHAR(MAX) 
EXTERNAL Name concat_assembly.[Concat.Concat]; 
GO  

sp_configure 'clr enabled', 1;
RECONFIGURE

Quan sát rằng đường dẫn đến lắp ráp có thể được truy cập đến máy chủ. Vì bạn đã thực hiện thành công tất cả các bước, bạn có thể sử dụng chức năng như:

SELECT dbo.Concat(field1, ',')
FROM Table1

Hy vọng nó giúp!!!


1
Liên kết DLL là một lỗi 404. Sử dụng một hội đồng cho việc này là quá mức cần thiết. Xem câu trả lời tốt nhất cho SQL Server.
Bảo vệ

6

MySQL hoàn thành Ví dụ:

Chúng tôi có Người dùng có thể có nhiều Dữ liệu và chúng tôi muốn có đầu ra, nơi chúng tôi có thể thấy tất cả Dữ liệu người dùng trong danh sách:

Kết quả:

___________________________
| id   |  rowList         |
|-------------------------|
| 0    | 6, 9             |
| 1    | 1,2,3,4,5,7,8,1  |
|_________________________|

Thiết lập bảng:

CREATE TABLE `Data` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;


INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);


CREATE TABLE `User` (
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `User` (`id`) VALUES
(0),
(1);

Truy vấn:

SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id

5

Tôi thường sử dụng select như thế này để nối chuỗi trong SQL Server:

with lines as 
( 
  select 
    row_number() over(order by id) id, -- id is a line id
    line -- line of text.
  from
    source -- line source
), 
result_lines as 
( 
  select 
    id, 
    cast(line as nvarchar(max)) line 
  from 
    lines 
  where 
    id = 1 
  union all 
  select 
    l.id, 
    cast(r.line + N', ' + l.line as nvarchar(max))
  from 
    lines l 
    inner join 
    result_lines r 
    on 
      l.id = r.id + 1 
) 
select top 1 
  line
from
  result_lines
order by
  id desc

5

Nếu bạn muốn xử lý null, bạn có thể làm điều đó bằng cách thêm mệnh đề where hoặc thêm COALESCE khác xung quanh cái đầu tiên.

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People

5

Điều này làm việc cho tôi ( SqlServer 2016 ):

SELECT CarNamesString = STUFF((
         SELECT ',' + [Name]
            FROM tbl_cars 
            FOR XML PATH('')
         ), 1, 1, '')

Đây là nguồn: https://www.mytecbits.com/

Và một giải pháp cho MySql (vì trang này hiển thị trong Google cho MySql)

SELECT [Name],
       GROUP_CONCAT(DISTINCT [Name]  SEPARATOR ',')
       FROM tbl_cars

Từ Tài liệu MySql



4

Điều này cũng có thể hữu ích

create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')

DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test

trả lại

Peter,Paul,Mary

5
Thật không may hành vi này dường như không được hỗ trợ chính thức. MSDN nói: "Nếu một biến được tham chiếu trong danh sách chọn, thì nó sẽ được gán một giá trị vô hướng hoặc câu lệnh SELECT chỉ nên trả về một hàng." Và có những người đã quan sát các vấn đề: sqlmag.com/sql-server/multi-row-variable-assocate-and-order
blueling
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.