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
?
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
?
Câu trả lời:
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 PATH
phươ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
<
hoặc &
. Xem bình luận của @ BenHinman.
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.
FOR XML
nhằm tạo ra XML, không nối các chuỗi tùy ý. Đó là lý do tại sao nó thoát &
, <
và >
để mã thực thể XML ( &
, <
, >
). Tôi giả sử nó cũng sẽ thoát "
và '
đến "
và '
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.
string_agg
vào v.Next. và tất cả những điều này có thể biến mất
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):
1) Không cần khởi tạo @Names
vớ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.
@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]).
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.
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;
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
+ ', '
nó, nó vẫn thêm một khoảng trống giữa mỗi phần tử được nối.
SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
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
Bạn có thể sử dụng chức năng STRING_AGG mới
<
, >
, &
, vv mà FOR XML PATH('')
sẽ tự động thoát ra.
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
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?
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_len
như được viết trong stackoverflow.com/a/1278210/1498405
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
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ó?
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 .
select array_to_string(array_agg(name||'('||id||')'
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.
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ự.
LISTAGG
hoạt động hoàn hảo! Chỉ cần đọc tài liệu liên kết ở đây. wm_concat
xóa từ phiên bản 12c trở đi.
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
<
hoặc &
.
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
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
SELECT @names = @names + ISNULL(' ' + Name, '')
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
name
cột vào một chuỗi bằng dấu phẩy cho 4 nhóm của id
s. 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.
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
SELECT string_agg(non_text_type::text, ',') FROM table
varchar
hoặcchar
Trong SQL Server vNext, điều này sẽ được tích hợp với chức năng STRING_AGG, đọc thêm về nó tại đây: https://msdn.microsoft.com/en-us/l Library / mt790580.aspx
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(''))
, ' ', ', ')
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.)
', '
bằng ','
nếu bạn không muốn có thêm không gian.
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
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à :-)
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
Đố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 đó.
Để 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
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)
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
sau đó bạn không phải cắt bớt nó sau đó.
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!!!
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
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
Đ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
Trong Oracle, nó là wm_concat
. Tôi tin rằng chức năng này có sẵn trong bản phát hành 10g và cao hơn.
Đ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