วิธีการเชื่อมข้อความจากหลายแถวเข้าด้วยกันเป็นสตริงข้อความเดียวในเซิร์ฟเวอร์ SQL


1912

พิจารณาชื่อตารางการถือครองฐานข้อมูลที่มีสามแถว:

Peter
Paul
Mary

มีวิธีง่าย ๆ ในการเปลี่ยนให้เป็นสตริงเดียวPeter, Paul, Maryหรือไม่?


26
สำหรับคำตอบเฉพาะของ SQL Server ลองคำถามนี้
Matt Hamilton

17
สำหรับ MySQL ลองดูGroup_Concatจากคำตอบนี้
Pykler

26
ฉันหวังว่า SQL Server เวอร์ชั่นถัดไปจะเสนอคุณสมบัติใหม่ในการแก้ปัญหาการเชื่อมต่อสตริงแบบหลายแถวอย่างหรูหราโดยไม่ต้องใช้ความสามารถของ FOR XML PATH
Pete Alvin

4
ไม่ใช่ SQL แต่ถ้านี่เป็นเพียงครั้งเดียวคุณสามารถวางรายการลงในเครื่องมือในเบราว์เซอร์นี้ได้ทาวน์ /column
Stack Man

3
ใน Oracle คุณสามารถใช้ LISTAGG (COLUMN_NAME) จาก 11g r2 ก่อนหน้านั้นได้มีฟังก์ชันที่ไม่รองรับที่เรียกว่า WM_CONCAT (COLUMN_NAME) ซึ่งทำเช่นเดียวกัน
Richard

คำตอบ:


1432

ถ้าคุณอยู่ใน SQL Server 2017 หรือ Azure ดูคำตอบ Mathieu Renda

ฉันมีปัญหาที่คล้ายกันเมื่อฉันพยายามเข้าร่วมสองตารางที่มีความสัมพันธ์แบบหนึ่งต่อหลายคน ใน SQL 2005 ฉันพบว่าXML PATHวิธีนี้สามารถจัดการการต่อแถวได้ง่ายมาก

หากมีตารางเรียกว่า STUDENTS

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

ผลลัพธ์ที่ฉันคาดไว้คือ:

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

ฉันใช้สิ่งต่อไปนี้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]

คุณสามารถทำสิ่งเดียวกันด้วยวิธีที่กะทัดรัดกว่านี้ถ้าคุณสามารถต่อคอมม่าที่จุดเริ่มต้นและใช้substringข้ามสิ่งแรกดังนั้นคุณไม่จำเป็นต้องทำแบบสอบถามย่อย:

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
ทางออกที่ดี ต่อไปนี้อาจจะเป็นประโยชน์ถ้าคุณต้องการที่จะจัดการกับตัวอักษรพิเศษเช่นผู้ที่อยู่ในรูปแบบ HTML: ร็อบลี่ย์: การจัดการตัวอักษรพิเศษกับเส้นทาง XML ( '')

10
เห็นได้ชัดว่านี้ไม่ทำงานถ้าชื่อประกอบด้วยอักขระ XML เช่นหรือ< &ดูความคิดเห็นของ @ BenHinman
Sam

23
หมายเหตุ: FOR XML PATH ('')วิธีการนี้สามารถพึ่งพาพฤติกรรมที่ไม่มีเอกสารของ ซึ่งหมายความว่าไม่ควรพิจารณาว่าเชื่อถือได้เนื่องจากแพตช์หรืออัปเดตใด ๆ อาจเปลี่ยนแปลงวิธีการทำงานของฟังก์ชั่นนี้ โดยพื้นฐานแล้วมันขึ้นอยู่กับคุณสมบัติที่ไม่รองรับ
เบคอน Bits

26
@Whelkaholism บรรทัดล่างคือFOR XMLมีไว้เพื่อสร้าง XML โดยไม่ต่อสายอักขระตามอำเภอใจ นั่นเป็นเหตุผลที่มันหนี &, <และ>รหัสนิติบุคคล XML ( &amp;, &lt;, &gt;) ฉันคิดว่ามันจะหลบหนี"และ'ไปที่&quot;และ&apos;ในคุณสมบัติเช่นกัน มันไม่ได้ GROUP_CONCAT() , string_agg(), array_agg(), listagg()ฯลฯ แม้ว่าคุณสามารถชนิดของการทำให้มันทำอย่างนั้น เราควรใช้เวลาในการเรียกร้องให้ Microsoft ใช้ฟังก์ชั่นที่เหมาะสม
เบคอน Bits

13
ข่าวดี: จะเพิ่ม MS SQL Server string_aggใน v.Next และทั้งหมดนี้สามารถหายไปได้
Jason C

1008

คำตอบนี้อาจส่งคืนผลลัพธ์ที่ไม่คาดคิดเพื่อให้ได้ผลลัพธ์ที่สอดคล้องกันให้ใช้วิธีการอย่างใดอย่างหนึ่งของ FOR XML PATH ที่มีรายละเอียดในคำตอบอื่น ๆ

ใช้COALESCE:

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

เพียงคำอธิบาย (เนื่องจากคำตอบนี้ดูเหมือนจะได้รับมุมมองที่ค่อนข้างปกติ):

  • การรวมตัวกันเป็นเพียงการโกงที่มีประโยชน์ซึ่งทำสองสิ่ง:

1) ไม่จำเป็นต้องเริ่มต้น@Namesด้วยค่าสตริงว่าง

2) ไม่จำเป็นต้องถอดตัวคั่นพิเศษออกในตอนท้าย

  • การแก้ปัญหาดังกล่าวข้างต้นจะให้ผลลัพธ์ที่ไม่ถูกต้องถ้าแถวมีโมฆะค่าชื่อ (ถ้ามีความเป็นโมฆะของโมฆะจะทำให้@Names เป็นโมฆะหลังจากแถวนั้นและแถวถัดไปจะเริ่มต้นมาเป็นสตริงที่ว่างเปล่าอีกครั้ง. แก้ไขได้อย่างง่ายดายด้วยการเป็นหนึ่งในสอง การแก้ปัญหา:
DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL

หรือ:

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

ขึ้นอยู่กับพฤติกรรมที่คุณต้องการ (ตัวเลือกแรกเพียงกรองNULLออกตัวเลือกที่สองเก็บไว้ในรายการด้วยข้อความทำเครื่องหมาย [แทนที่ 'N / A' ด้วยสิ่งที่เหมาะสมสำหรับคุณ])


72
เพื่อให้ชัดเจนขึ้นการรวมกันไม่เกี่ยวข้องกับการสร้างรายการเพียงแค่ตรวจสอบให้แน่ใจว่าไม่มีค่า NULL
แกรมเพอร์โรว์

17
@ Graeme Perrow มันไม่ได้ยกเว้นค่า NULL (จำเป็นต้องมี WHERE สำหรับสิ่งใด - สิ่งนี้จะสูญเสียผลลัพธ์หากหนึ่งในค่าอินพุตเป็น NULL) และเป็นสิ่งจำเป็นในวิธีการนี้เพราะ: NULL + non-NULL -> NULL และไม่ใช่ NULL + NULL -> NULL; ยัง @Name เป็น NULL โดยค่าเริ่มต้นและที่จริงแล้วคุณสมบัตินั้นถูกใช้เป็น Sentinel โดยปริยายที่นี่เพื่อตรวจสอบว่าควรจะเพิ่ม ',' หรือไม่

62
โปรดทราบว่าวิธีการต่อข้อมูลนี้ใช้ SQL Server ในการดำเนินการสืบค้นด้วยแผนเฉพาะ ฉันถูกจับได้ว่าใช้วิธีนี้ (ด้วยการเพิ่มคำสั่งซื้อ) เมื่อมันจัดการกับแถวจำนวนน้อยมันก็ทำงานได้ดี แต่ด้วยข้อมูล SQL Server มากขึ้นก็เลือกแผนที่แตกต่างกันซึ่งส่งผลให้การเลือกรายการแรกโดยไม่มีการต่อข้อมูลใด ๆ ดูบทความนี้โดย Anith ส.ว.
fbarber

17
วิธีการนี้ไม่สามารถใช้เป็นแบบสอบถามย่อยในรายการที่เลือกหรือ where-clause เนื่องจากใช้ตัวแปร tSQL ในกรณีเช่นนี้คุณสามารถใช้วิธีการที่เสนอโดย @Ritesh
R. Schreurs

11
นี่ไม่ใช่วิธีการต่อข้อมูลที่เชื่อถือได้ ไม่ได้รับการสนับสนุนและไม่ควรใช้ (ต่อ Microsoft เช่นsupport.microsoft.com/en-us/kb/287515 , connect.microsoft.com/SQLServer/Feedback/Details/704389 ) มันสามารถเปลี่ยนแปลงได้โดยไม่มีการเตือน ใช้เทคนิค XML PATH ที่กล่าวถึงในstackoverflow.com/questions/5031204/ …ฉันเขียนเพิ่มเติมได้ที่นี่: marc.durdin.net/2015/07/…
Marc Durdin

454

SQL Server 2017+ และ SQL Azure: STRING_AGG

เริ่มต้นด้วย SQL Server เวอร์ชั่นถัดไปในที่สุดเราก็สามารถเชื่อมโยงข้ามแถวได้โดยไม่ต้องเปลี่ยนไปใช้ตัวแปรหรือ XML ของคาถา

STRING_AGG (Transact-SQL)

โดยไม่ต้องจัดกลุ่ม

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

ด้วยการจัดกลุ่ม:

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

ด้วยการจัดกลุ่มและการจัดเรียงย่อย

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

2
และไม่เหมือนกับโซลูชัน CLR คุณสามารถควบคุมการเรียงลำดับได้
ศีล

ดูเหมือนว่าจะมีข้อ จำกัด ในการแสดงอักขระ 4000 ตัวบน STRING_AGG
Inspired โดย

มีวิธีการเรียงลำดับหรือไม่ในกรณีที่ไม่มี GROUP BY (เช่นสำหรับตัวอย่าง "Without grouping")?
RuudvK

อัปเดต: ฉันพยายามทำสิ่งต่อไปนี้ แต่มีวิธีที่สะอาดกว่านี้ไหม SELECT STRING_AGG (ชื่อ, ',') เป็นแผนกจาก (เลือกชื่อ 100000 บนสุดจาก HumanResources.Department เรียงตามชื่อ) D;
RuudvK

362

วิธีหนึ่งที่ยังไม่แสดงผ่านXML data()คำสั่งใน MS SQL Server คือ:

สมมติว่าตารางชื่อ NameList โดยมีหนึ่งคอลัมน์ชื่อ FName

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

ผลตอบแทน:

"Peter, Paul, Mary, "

ต้องดำเนินการเฉพาะเครื่องหมายจุลภาคพิเศษเท่านั้น

แก้ไข:ตามที่นำมาจากความคิดเห็นของ @ NReilingh คุณสามารถใช้วิธีการต่อไปนี้เพื่อลบเครื่องหมายจุลภาคต่อท้าย สมมติว่าชื่อตารางและคอลัมน์เดียวกัน:

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

15
ศักดิ์สิทธิ์ s ** ที่น่าอัศจรรย์! เมื่อดำเนินการด้วยตัวเองเช่นเดียวกับในตัวอย่างของคุณผลลัพธ์จะถูกจัดรูปแบบเป็นไฮเปอร์ลิงก์ซึ่งเมื่อคลิก (ใน SSMS) จะเปิดหน้าต่างใหม่ที่มีข้อมูล แต่เมื่อใช้เป็นส่วนหนึ่งของเคียวรีที่ใหญ่กว่ามันจะปรากฏเป็นสตริง มันเป็นสตริงหรือไม่? หรือเป็น xml ที่ฉันต้องปฏิบัติแตกต่างกันในแอปพลิเคชันที่จะใช้ข้อมูลนี้หรือไม่?
Ben

10
วิธีการนี้ยังใช้อักขระแบบ XML-escapes เช่น <และ> ดังนั้นการเลือก '<b>' + FName + '</b>' จะส่งผลให้ "& lt; b & gt; John & lt; / b & gt; & lt; b & gt; Paul ... "
LukášLánský

8
ทางออกที่เรียบร้อย ฉันสังเกตเห็นว่าแม้ว่าฉันจะไม่เพิ่ม+ ', 'มันก็ยังเพิ่มช่องว่างเดียวระหว่างองค์ประกอบที่ตัดแบ่งทั้งหมด
Baodad

8
@ Baodad นั่นดูเหมือนจะเป็นส่วนหนึ่งของข้อตกลง คุณสามารถแก้ไขได้ด้วยการแทนที่อักขระโทเค็นที่เพิ่มเข้ามา ตัวอย่างเช่นนี่เป็นรายการที่คั่นด้วยจุลภาคที่สมบูรณ์แบบสำหรับทุกความยาว:SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
NReilingh

1
ว้าวจริง ๆ แล้วในการทดสอบของฉันโดยใช้ data () และการแทนที่เป็นวิธีที่มีประสิทธิภาพมากกว่าไม่ใช่ สุดแปลก
NReilingh

306

ในSQL Server 2005

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

ใน SQL Server 2016

คุณสามารถใช้ไวยากรณ์ FOR JSON

กล่าวคือ

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

และผลลัพธ์จะกลายเป็น

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

สิ่งนี้จะใช้ได้แม้ข้อมูลของคุณจะมีอักขระ XML ที่ไม่ถูกต้อง

'"},{"_":"'มีความปลอดภัยเพราะถ้าข้อมูลที่คุณมี'"},{"_":"',มันจะหนีไป"},{\"_\":\"

คุณสามารถแทนที่', 'ด้วยตัวคั่นสตริงใด ๆ


และใน SQL Server 2017, Azure SQL Database

คุณสามารถใช้ฟังก์ชัน STRING_AGGใหม่


3
ใช้ฟังก์ชั่น STUFF ได้ดีเพื่อจับตัวละครทั้งสอง
เดวิด

3
ฉันชอบโซลูชันนี้ที่สุดเพราะฉันสามารถใช้งานได้อย่างง่ายดายในรายการที่เลือกโดยการต่อท้ายเป็น ฉันไม่แน่ใจว่าจะทำอย่างไรกับวิธีแก้ปัญหาของ @Ritesh
R. Schreurs

13
นี้จะดีกว่าคำตอบที่ได้รับการยอมรับเพราะตัวเลือกนี้ยังจับยกเลิกการหลบหนีตัวอักษร reserverd XML เช่น<, >, &ฯลฯ ซึ่งFOR XML PATH('')จะหลบหนีโดยอัตโนมัติ
BateTech

นี่เป็นคำตอบที่ยอดเยี่ยมเพราะมันแก้ปัญหาและให้วิธีที่ดีที่สุดในการทำสิ่งต่าง ๆ ใน SQL เวอร์ชันต่าง ๆ ตอนนี้ฉันหวังว่าฉันจะใช้ 2017 / Azure
Chris Ward

120

ใน MySQL มีฟังก์ชั่นGROUP_CONCAT ()ซึ่งช่วยให้คุณสามารถเชื่อมค่าจากหลายแถวเข้าด้วยกัน ตัวอย่าง:

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 '", "'ฉันจะพลาดบางตัวอักษรในตอนท้ายของรายการสุดท้าย ทำไมสิ่งนี้ถึงเกิดขึ้นได้?
gooleem

@gooleem ฉันไม่ชัดเจนในสิ่งที่คุณหมายถึง แต่ฟังก์ชั่นนี้จะทำให้ตัวคั่นระหว่างรายการเท่านั้นไม่ใช่หลังจาก หากไม่ใช่คำตอบฉันขอแนะนำให้โพสต์คำถามใหม่
Darryl Hein

@DarrylHein สำหรับความต้องการของฉันฉันใช้ตัวคั่นตามด้านบน แต่นี่จะลดตัวอักษรบางตัวที่ส่วนท้ายสุดของเอาต์พุต นี่เป็นสิ่งที่แปลกมากและดูเหมือนว่าจะเป็นแมลง ฉันไม่มีวิธีแก้ปัญหาฉันเพิ่งแก้ไขได้
gooleem

ใช้งานได้จริง สองสิ่งที่ควรพิจารณา: 1) หากคอลัมน์ของคุณไม่ใช่CHAR, คุณต้องส่งมันเช่นผ่านทางGROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')2) หากคุณมีค่ามากมายที่เข้ามาคุณควรเพิ่มgroup_concat_max_lenตามที่เขียนไว้ในstackoverflow.com/a/1278210/1498405
hardmooth

58

ใช้COALESCE - เรียนรู้เพิ่มเติมจากที่นี่

ตัวอย่างเช่น

102

103

104

จากนั้นเขียนรหัสด้านล่างในเซิร์ฟเวอร์ 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

ผลผลิตจะเป็น:

102,103,104

2
นี่เป็นทางออกที่ดีที่สุดสำหรับ IMO เพราะหลีกเลี่ยงปัญหาการเข้ารหัสที่ FOR XML นำเสนอ ฉันใช้Declare @Numbers AS Nvarchar(MAX)และมันก็ใช้ได้ดี คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงไม่แนะนำให้ใช้
EvilDr

7
โซลูชันนี้มีการโพสต์เมื่อ 8 ปีที่แล้ว! stackoverflow.com/a/194887/986862
Andre Figueiredo

เหตุใดแบบสอบถามนี้จึงส่งคืน ??? สัญลักษณ์แทน Cyrillic นี่เป็นเพียงปัญหาเกี่ยวกับผลผลิตหรือไม่
Akmal Salikhov

48

อาร์เรย์ Postgres ยอดเยี่ยม ตัวอย่าง:

สร้างข้อมูลทดสอบ:

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)

รวมไว้ในอาร์เรย์:

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

แปลงอาร์เรย์เป็นสตริงที่คั่นด้วยเครื่องหมายจุลภาค:

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

DONE

ตั้งแต่ PostgreSQL 9.0 มันเป็นเรื่องง่ายยิ่งขึ้น


หากคุณต้องการมากกว่าหนึ่งคอลัมน์เช่นรหัสพนักงานของพวกเขาในวงเล็บใช้ประกอบ concat: select array_to_string(array_agg(name||'('||id||')'
ริชาร์ดฟ็อกซ์

ไม่สามารถใช้ได้กับsql-server , เฉพาะกับmysql
GoldBishop

46

Oracle 11g รีลีส 2 รองรับฟังก์ชั่น LISTAGG เอกสารที่นี่

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.

คำเตือน

ใช้ความระมัดระวังในการใช้ฟังก์ชั่นนี้หากมีความเป็นไปได้ที่สตริงผลลัพธ์จะมีความยาวเกิน 4,000 อักขระ มันจะโยนข้อยกเว้น หากเป็นกรณีนี้คุณต้องจัดการกับข้อยกเว้นหรือหมุนฟังก์ชั่นของคุณเองที่ป้องกันไม่ให้สตริงที่เข้าร่วมไปเกิน 4,000 ตัวอักษร


1
สำหรับ Oracle เวอร์ชันเก่า wm_concat นั้นสมบูรณ์แบบ การใช้อธิบายในของขวัญลิงค์โดยอเล็กซ์ ขอบคุณอเล็กซ์!
toscanelli

LISTAGGทำงานได้สมบูรณ์แบบ! เพียงอ่านเอกสารที่ลิงก์ไว้ที่นี่ wm_concatลบออกจากรุ่น 12c เป็นต้นไป
asgs

34

ใน SQL Server 2005 และใหม่กว่าใช้แบบสอบถามด้านล่างเพื่อต่อแถว

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
ผมเชื่อว่านี่ล้มเหลวเมื่อค่าที่มีสัญลักษณ์ XML เช่นหรือ< &
แซม

28

ฉันไม่มีสิทธิ์เข้าถึง SQL Server ที่บ้านดังนั้นฉันเดาว่าที่นี่ แต่มันมากหรือน้อย:

DECLARE @names VARCHAR(500)

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

11
คุณจะต้องเริ่มต้น @ names ให้กับสิ่งที่ไม่เป็นโมฆะมิฉะนั้นคุณจะได้รับ NULL ตลอด คุณต้องจัดการกับตัวคั่นด้วย (รวมถึงสิ่งที่ไม่จำเป็น)
Marc Gravell

3
ปัญหาเดียวของวิธีนี้ (ซึ่งฉันใช้ตลอดเวลา) คือคุณไม่สามารถฝังมันได้
ekkis

1
เพื่อกำจัดพื้นที่ชั้นนำให้เปลี่ยนข้อความค้นหาเป็นSELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
Tian van Heerden

นอกจากนี้คุณยังจะต้องตรวจสอบว่าชื่อไม่ได้เป็นโมฆะ, คุณสามารถทำมันได้โดยการทำ:SELECT @names = @names + ISNULL(' ' + Name, '')
Vita1ij

28

แนะนำให้ใช้โซลูชัน CTE แบบเรียกซ้ำ แต่ไม่มีรหัสให้ รหัสด้านล่างเป็นตัวอย่างของ CTE แบบเรียกซ้ำ ทราบว่าแม้ว่าผลลัพธ์ที่ตรงกับคำถามที่ว่าข้อมูลไม่ค่อนข้างตรงกับคำอธิบายที่ได้รับในขณะที่ฉันคิดว่าคุณอยากจะทำเช่นนี้ที่กลุ่มของแถวไม่ได้ทุกแถวในตาราง การเปลี่ยนเพื่อให้ตรงกับแถวทั้งหมดในตารางจะถูกทิ้งไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน

;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
สำหรับงุนงง: แทรกแบบสอบถามนี้ 12 แถว (3 คอลัมน์) เป็น basetable ชั่วคราวแล้วสร้าง recursive ตารางสามัญ Expression (rCTE) แล้ว flattens name คอลัมน์ลงในสตริงคั่นด้วยเครื่องหมายจุลภาค 4 กลุ่มของids ฉันคิดว่านี่เป็นงานมากกว่าโซลูชันอื่น ๆ ส่วนใหญ่สำหรับ SQL Server
knb

2
@ knb: ไม่แน่ใจว่านั่นคือการยกย่องการลงโทษหรือเพียงแค่แปลกใจ ตารางฐานเป็นเพราะฉันชอบตัวอย่างที่ใช้งานได้จริงมันไม่มีอะไรเกี่ยวข้องกับคำถาม
jmoreno

26

คุณต้องสร้างตัวแปรที่จะเก็บผลลัพธ์สุดท้ายของคุณและเลือกไว้เช่นนั้น

ทางออกที่ง่ายที่สุด

DECLARE @char VARCHAR(MAX);

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

PRINT @char;

25

เริ่มต้นด้วย PostgreSQL 9.0 ซึ่งค่อนข้างง่าย:

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

ในรุ่นก่อน 9.0 array_agg()สามารถใช้งานได้ตามที่แสดงโดย hgmnz


การทำเช่นนี้มีคอลัมน์ที่ไม่ได้เป็นประเภทข้อความคุณต้องเพิ่มหล่อพิมพ์:SELECT string_agg(non_text_type::text, ',') FROM table
Torben Kohlmeier

@TorbenKohlmeier: คุณต้องการคอลัมน์ที่ไม่ใช่อักขระเท่านั้น (เช่นจำนวนเต็ม, ทศนิยม) มันใช้งานได้ดีสำหรับvarcharหรือchar
a_horse_with_no_name


18

การใช้ XML ช่วยให้ฉันสามารถแยกแถวด้วยเครื่องหมายจุลภาคได้ สำหรับเครื่องหมายจุลภาคพิเศษเราสามารถใช้ฟังก์ชั่นแทนที่ของ SQL Server แทนที่จะเพิ่มเครื่องหมายจุลภาคการใช้ AS 'data ()' จะต่อแถวกับช่องว่างซึ่งภายหลังสามารถแทนที่ด้วยเครื่องหมายจุลภาคเป็นไวยากรณ์ที่เขียนด้านล่าง

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

2
นี่คือคำตอบที่ดีที่สุดที่นี่ใน Verifyon ของฉัน การใช้ประกาศตัวแปรไม่ดีเมื่อคุณต้องการเข้าร่วมในตารางอื่นและนี่เป็นสิ่งที่ดีและสั้น การทำงานที่ดี.
David Roussel

7
มันใช้งานไม่ได้ถ้าข้อมูล FName มีช่องว่างอยู่แล้วตัวอย่างเช่น "My Name"
binball

ใช้งานได้จริงสำหรับฉันใน ms-sql 2016 เลือก REPLACE ((เลือกชื่อ AS 'data ()' จากแบรนด์ Where Id IN (1,2,3,4) สำหรับเส้นทาง xml ('')), '', ' , ') เป็นแบรนด์ทั้งหมด
Rejwanul Reja

17

โซลูชันที่พร้อมใช้งานโดยไม่มีเครื่องหมายจุลภาคพิเศษ:

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

รายการว่างจะส่งผลให้ค่า NULL โดยปกติคุณจะแทรกรายการลงในคอลัมน์ตารางหรือตัวแปรโปรแกรม: ปรับความยาวสูงสุด 255 ความต้องการของคุณ

(Diwakar และ Jens Frandsen ให้คำตอบที่ดี แต่ต้องการการปรับปรุง)


มีช่องว่างอยู่ก่อนเครื่องหมายจุลภาคเมื่อใช้สิ่งนี้ :(
slayernoah

1
เพียงแทนที่', 'ด้วย','ถ้าคุณไม่ต้องการพื้นที่เพิ่ม
Daniel Reis

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

นี่คือตัวอย่าง:

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)

สิ่งนี้ทำให้จุลภาคหลงทางที่จุดเริ่มต้น

อย่างไรก็ตามหากคุณต้องการคอลัมน์อื่น ๆ หรือเป็นตารางลูกคุณจะต้องล้อมรอบคอลัมน์นี้ในฟิลด์ผู้ใช้กำหนดสเกลาร์ (UDF)

คุณสามารถใช้เส้นทาง XML เป็นแบบสอบถามย่อยที่มีความสัมพันธ์ในส่วนคำสั่ง SELECT ได้เช่นกัน (แต่ฉันต้องรอจนกว่าฉันจะกลับไปทำงานเพราะ Google ไม่ทำงานที่บ้าน :-)


10

ด้วยคำตอบอื่น ๆ ผู้ที่อ่านคำตอบจะต้องทราบถึงตารางโดเมนที่เฉพาะเจาะจงเช่นยานพาหนะหรือนักเรียน ตารางจะต้องสร้างและเติมด้วยข้อมูลเพื่อทดสอบวิธีแก้ปัญหา

ด้านล่างเป็นตัวอย่างที่ใช้ตาราง "Information_Schema.Columns" ของ SQL Server ด้วยการใช้โซลูชันนี้ไม่จำเป็นต้องสร้างตารางหรือเพิ่มข้อมูล ตัวอย่างนี้สร้างรายการชื่อคอลัมน์ที่คั่นด้วยเครื่องหมายจุลภาคสำหรับตารางทั้งหมดในฐานข้อมูล

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

สำหรับ Oracle DB ให้ดูที่คำถามนี้: การผสานหลายแถวเข้าใน Oracle ได้อย่างไรโดยไม่ต้องสร้างโพรซีเดอร์ที่เก็บไว้?

คำตอบที่ดีที่สุดคือ @Emmanuel โดยใช้ฟังก์ชัน LISTAGG () ในตัวซึ่งมีอยู่ใน Oracle 11g รีลีส 2 และใหม่กว่า

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

ตามที่ @ user762952 ชี้ให้เห็นและตามเอกสารของออราเคิลhttp://www.oracle-base.com/articles/misc/string-aggregation-techniques.phpฟังก์ชั่น WM_CONCAT () ก็เป็นตัวเลือกเช่นกัน ดูเหมือนว่าจะมีความเสถียร แต่ Oracle แนะนำอย่างชัดเจนให้ใช้กับแอพพลิเคชั่น SQL ใด ๆ ดังนั้นให้ใช้ความเสี่ยงของคุณเอง

นอกจากนั้นคุณจะต้องเขียนฟังก์ชั่นของคุณเอง เอกสาร Oracle ข้างต้นมีคำแนะนำเกี่ยวกับวิธีการทำเช่นนั้น



7

ฉันชอบความงดงามของมันคำตอบของ Dana แค่อยากทำให้มันสมบูรณ์

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 = \ @ ชื่อ + ชื่อ +', 'FROM Names') ด้วยวิธีนี้สองตัวสุดท้ายจะเป็น ','
JT_

ในกรณีของฉันฉันจำเป็นต้องกำจัดเครื่องหมายจุลภาคชั้นนำดังนั้นเปลี่ยนแบบสอบถามเป็นSELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Namesแล้วคุณไม่ต้องตัดทอนหลังจากนั้น
Tian van Heerden

6

คำตอบนี้จะต้องใช้สิทธิ์พิเศษในเซิร์ฟเวอร์ในการทำงาน

แอสเซมบลีเป็นตัวเลือกที่ดีสำหรับคุณ มีเว็บไซต์จำนวนมากที่อธิบายถึงวิธีการสร้าง อย่างใดอย่างหนึ่งที่ผมคิดว่าจะมีการอธิบายอย่างดีนี้เป็นหนึ่งใน

ถ้าคุณต้องการฉันได้สร้างชุดประกอบขึ้นมาและสามารถดาวน์โหลด DLL ได้ที่นี่ที่นี่

เมื่อคุณดาวน์โหลดแล้วคุณจะต้องเรียกใช้สคริปต์ต่อไปนี้ใน SQL Server ของคุณ:

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

สังเกตว่าเส้นทางไปยังแอสเซมบลีอาจเข้าถึงเซิร์ฟเวอร์ได้ เนื่องจากคุณทำตามขั้นตอนทั้งหมดเรียบร้อยแล้วคุณสามารถใช้ฟังก์ชันดังนี้:

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

หวังว่ามันจะช่วย !!!


1
ลิงก์ DLL เป็นข้อผิดพลาด 404 การใช้ชุดประกอบสำหรับสิ่งนี้เกินความจริง ดูคำตอบที่ดีที่สุดสำหรับ SQL Server
Protiguous

6

ตัวอย่าง MySQL ที่สมบูรณ์:

เรามีผู้ใช้ที่สามารถมีข้อมูลจำนวนมากและเราต้องการที่จะมีผลลัพธ์ที่เราสามารถเห็นผู้ใช้ทั้งหมดในรายการ:

ผลลัพธ์:

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

การตั้งค่าตาราง:

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

ค้นหา:

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

ฉันมักจะใช้ select เช่นนี้เพื่อเชื่อมสตริงใน 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

หากคุณต้องการจัดการกับโมฆะคุณสามารถทำได้โดยการเพิ่มส่วนคำสั่งที่หรือเพิ่มอีก COALESCE รอบแรก

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

5

สิ่งนี้ใช้ได้สำหรับฉัน ( SqlServer 2016 ):

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

นี่คือแหล่งที่มา: https://www.mytecbits.com/

และทางออกสำหรับMySql (เนื่องจากหน้านี้แสดงใน Google สำหรับ MySql)

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

จากเอกสาร MySql



4

สิ่งนี้มีประโยชน์เช่นกัน

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

ผลตอบแทน

Peter,Paul,Mary

5
น่าเสียดายที่พฤติกรรมนี้ดูเหมือนจะไม่ได้รับการสนับสนุนอย่างเป็นทางการ MSDNพูดว่า: "หากตัวแปรอ้างอิงในรายการที่เลือกควรกำหนดค่าสเกลาร์หรือคำสั่ง SELECT ควรส่งคืนหนึ่งแถวเท่านั้น" และมีคนที่สังเกตเห็นปัญหา: sqlmag.com/sql-server/multi-row-variable-assignment-and-order
blueling
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.