Cách thức hoạt động và 'Đối với đường dẫn Xml' trong Sql Server


367

Bảng là:

+----+------+
| Id | Name |
+----+------+    
| 1  | aaa  |
| 1  | bbb  |
| 1  | ccc  |
| 1  | ddd  |
| 1  | eee  |
+----+------+

Yêu cầu đầu ra:

+----+---------------------+
| Id |        abc          |
+----+---------------------+ 
|  1 | aaa,bbb,ccc,ddd,eee |
+----+---------------------+

Truy vấn:

SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Truy vấn này đang hoạt động đúng. Nhưng tôi chỉ cần giải thích làm thế nào nó hoạt động hoặc có cách nào khác hoặc ngắn để làm điều này.

Tôi đang rất bối rối để hiểu điều này.



1
Tôi đã tạo một trang SqlFiddle cho việc này, để thấy nó hoạt động trong cuộc sống thực. Hy vọng nó sẽ giúp người khác.
Sabuncu

1
^ Có lẽ IDlà duy nhất trong một bảng khác nhau của các thực thể khác nhau và bảng này đang lưu trữ những thứ thuộc về chúng.
Nick Rolando

Truy vấn này không hoạt động nếu một số hàng có Id khác nhau. ví dụ: nếu 'ddd' và 'eee' có Id 2.
KevinVictor

10
Thời gian cho chuyến thăm hàng tháng của tôi đến trang này để xem tôi đã sai ở đâu.
Taylor Ackley

Câu trả lời:


683

Đây là cách nó làm việc:

1. Nhận chuỗi phần tử XML bằng FOR XML

Việc thêm FOR XML PATH vào cuối truy vấn cho phép bạn xuất kết quả của truy vấn dưới dạng các phần tử XML, với tên thành phần có trong đối số PATH. Ví dụ: nếu chúng ta chạy câu lệnh sau:

SELECT ',' + name 
              FROM temp1
              FOR XML PATH ('')

Bằng cách chuyển vào một chuỗi trống (FOR XML PATH ('')), thay vào đó, chúng ta có được những điều sau:

,aaa,bbb,ccc,ddd,eee

2. Xóa dấu phẩy hàng đầu bằng STUFF

Câu lệnh STUFF theo nghĩa đen là "nhét một chuỗi vào một chuỗi khác, thay thế các ký tự trong chuỗi đầu tiên. Tuy nhiên, chúng tôi đang sử dụng nó để loại bỏ ký tự đầu tiên của danh sách giá trị kết quả.

SELECT abc = STUFF((
            SELECT ',' + NAME
            FROM temp1
            FOR XML PATH('')
            ), 1, 1, '')
FROM temp1

Các tham số STUFFlà:

  • Chuỗi được nhồi bông nhồi bông (trong trường hợp của chúng tôi là danh sách đầy đủ tên với dấu phẩy hàng đầu)
  • Vị trí để bắt đầu xóa và chèn các ký tự (1, chúng tôi đang nhồi vào một chuỗi trống)
  • Số lượng ký tự cần xóa (1, là dấu phẩy hàng đầu)

Vì vậy, chúng tôi kết thúc với:

aaa,bbb,ccc,ddd,eee

3. Tham gia vào id để có danh sách đầy đủ

Tiếp theo, chúng ta chỉ cần tham gia vào danh sách id trong bảng tạm thời để có danh sách ID có tên:

SELECT ID,  abc = STUFF(
             (SELECT ',' + name 
              FROM temp1 t1
              WHERE t1.id = t2.id
              FOR XML PATH (''))
             , 1, 1, '') from temp1 t2
group by id;

Và chúng tôi có kết quả của chúng tôi:

-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

Hi vọng điêu nay co ich!


57
Bạn nên làm việc cho nhóm tài liệu của Microsoft (nếu có)
Fandango68

55
@ Fandango68, @ FutbolFan - Anh ấy không thể làm việc cho nhóm tài liệu của Microsoft. Giải thích của ông là quá rõ ràng và quá trực tiếp. ;-)
Chris

1
@ChrisProsser Tôi đồng ý. Oracle đã đi trước Microsoft về điều này bằng cách giới thiệu LISTAGGchức năng trong Oracle 11gR2. Tôi bỏ lỡ chức năng đó vào những ngày mà tôi phải sử dụng nó thay thế. techonthenet.com/oracle/fifts/listagg.php
FutbolFan

2
Xin chào. Trong bước 1, nếu bạn thực hiện: CHỌN tên TỪ temp1 CHO XML PATH ('') ... bạn nhận được <name> aaa </ name> <name> bbb </ name> ... vv ... Tôi đã không ' Lúc đầu, tôi nhận ra điều này ... Thay đổi nó thành CHỌN '' + tên ... vv ... sẽ xóa các thẻ.
KevinVictor

1
@ChrisProsser - Sybase ASA đã có listchức năng trong nhiều thập kỷ. Thật không may, Microsoft dựa trên SQLServer trên ASE của Sybase và không bao giờ bận tâm đến chức năng danh sách cho đến năm ngoái. Tôi đồng ý - đó là tâm trí bực bội. Và sau đó họ làm, họ gọi nó string_agg. Tôi đã nghĩ listlà khá rõ ràng.
youcantryreachingme

75

Bài viết này trình bày nhiều cách khác nhau để nối các chuỗi trong SQL, bao gồm cả phiên bản cải tiến của mã của bạn không mã hóa các giá trị được nối.

SELECT ID, abc = STUFF
(
    (
        SELECT ',' + name
        FROM temp1 As T2
        -- You only want to combine rows for a single ID here:
        WHERE T2.ID = T1.ID
        ORDER BY name
        FOR XML PATH (''), TYPE
    ).value('.', 'varchar(max)')
, 1, 1, '')
FROM temp1 As T1
GROUP BY id

Để hiểu những gì đang xảy ra, hãy bắt đầu với truy vấn bên trong:

SELECT ',' + name
FROM temp1 As T2
WHERE T2.ID = 42 -- Pick a random ID from the table
ORDER BY name
FOR XML PATH (''), TYPE

Vì bạn đang chỉ định FOR XML, bạn sẽ nhận được một hàng chứa một đoạn XML đại diện cho tất cả các hàng.

Vì bạn chưa chỉ định bí danh cột cho cột đầu tiên, mỗi hàng sẽ được bọc trong một phần tử XML với tên được chỉ định trong ngoặc sau FOR XML PATH. Ví dụ: nếu bạn có FOR XML PATH ('X'), bạn sẽ nhận được một tài liệu XML giống như:

<X>,aaa</X>
<X>,bbb</X>
...

Nhưng, vì bạn chưa chỉ định tên thành phần, bạn chỉ cần lấy danh sách các giá trị:

,aaa,bbb,...

Đơn .value('.', 'varchar(max)')giản chỉ cần lấy giá trị từ đoạn XML kết quả, mà không mã hóa XML bất kỳ ký tự "đặc biệt" nào. Bây giờ bạn có một chuỗi trông giống như:

',aaa,bbb,...'

Sau STUFFđó, hàm sẽ xóa dấu phẩy hàng đầu, cho bạn kết quả cuối cùng trông như sau:

'aaa,bbb,...'

Thoạt nhìn có vẻ khá khó hiểu, nhưng nó có xu hướng hoạt động khá tốt so với một số tùy chọn khác.


2
Việc sử dụng Type trong truy vấn của bạn là gì. Tôi nghĩ rằng để xác định, kết quả của đường dẫn XML sẽ được lưu trữ theo giá trị (không chắc chắn giải thích nếu sai).
Puneet Chawla

8
@PuneetChawla: Lệnh TYPEnày yêu cầu SQL trả về dữ liệu bằng cách sử dụng xmlloại. Không có nó, dữ liệu được trả về như một nvarchar(max). Nó được sử dụng ở đây để tránh các vấn đề mã hóa XML nếu có các ký tự đặc biệt trong namecột.
Richard Deeming

2
@barlop: Như bài viết SimpleTalk giải thích, nếu bạn bỏ TYPE.value('.', 'varchar(max)'), thì bạn có thể kết thúc với các thực thể được mã hóa XML trong kết quả.
Richard Deeming

1
@RichardDeeming có nghĩa là nếu dữ liệu chứa hoặc có thể chứa dấu ngoặc nhọn?
barlop

1
Nhưng, vì bạn chưa chỉ định tên thành phần, bạn chỉ cần lấy danh sách các giá trị , đây là cái nhìn sâu sắc mà tôi đã thiếu. Cảm ơn bạn.
Adam

44

Chế độ PATH được sử dụng trong việc tạo XML từ truy vấn SELECT

1. SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH;  

Ouput:
<row>
<ID>1</ID>
<Name>aaa</Name>
</row>

<row>
<ID>1</ID>
<Name>bbb</Name>
</row>

<row>
<ID>1</ID>
<Name>ccc</Name>
</row>

<row>
<ID>1</ID>
<Name>ddd</Name>
</row>

<row>
<ID>1</ID>
<Name>eee</Name>
</row>

Đầu ra là XML trung tâm phần tử trong đó mỗi giá trị cột trong hàng kết quả được gói trong một phần tử hàng. Vì mệnh đề SELECT không chỉ định bất kỳ bí danh nào cho tên cột, nên tên phần tử con được tạo giống với tên cột tương ứng trong mệnh đề SELECT.

Đối với mỗi hàng trong hàng, một thẻ được thêm vào.

2.
SELECT   
       ID,  
       Name  
FROM temp1
FOR XML PATH('');

Ouput:
<ID>1</ID>
<Name>aaa</Name>
<ID>1</ID>
<Name>bbb</Name>
<ID>1</ID>
<Name>ccc</Name>
<ID>1</ID>
<Name>ddd</Name>
<ID>1</ID>
<Name>eee</Name>

Đối với Bước 2: Nếu bạn chỉ định chuỗi có độ dài bằng không, phần tử gói không được tạo.

3. 

    SELECT   

           Name  
    FROM temp1
    FOR XML PATH('');

    Ouput:
    <Name>aaa</Name>
    <Name>bbb</Name>
    <Name>ccc</Name>
    <Name>ddd</Name>
    <Name>eee</Name>

4. SELECT   
        ',' +Name  
FROM temp1
FOR XML PATH('')

Ouput:
,aaa,bbb,ccc,ddd,eee

Trong Bước 4, chúng tôi đang nối các giá trị.

5. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1

Ouput:
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee
1   ,aaa,bbb,ccc,ddd,eee


6. SELECT ID,
    abc = (SELECT   
            ',' +Name  
    FROM temp1
    FOR XML PATH('') )
FROM temp1 GROUP by iD

Ouput:
ID  abc
1   ,aaa,bbb,ccc,ddd,eee

Ở Bước 6, chúng tôi sẽ nhóm ngày theo ID.

STUFF (source_ chuỗi, bắt đầu, độ dài, add_ chuỗi) Tham số hoặc Đối số source_ chuỗi Chuỗi nguồn cần sửa đổi. start Vị trí trong source_ chuỗi để xóa các ký tự có độ dài và sau đó chèn add_ chuỗi. length Số lượng ký tự cần xóa từ source_ chuỗi. add_opes Chuỗi ký tự cần chèn vào source_ chuỗi tại vị trí bắt đầu.

SELECT ID,
    abc = 
    STUFF (
        (SELECT   
                ',' +Name  
        FROM temp1
        FOR XML PATH('')), 1, 1, ''
    )
FROM temp1 GROUP by iD

Output:
-----------------------------------
| Id        | Name                |
|---------------------------------|
| 1         | aaa,bbb,ccc,ddd,eee |
-----------------------------------

1
Bạn viết "Trong Bước 4, chúng tôi đang nối các giá trị." Nhưng không rõ tại sao / làm thế nào ','cột được chỉ định, kết hợp với ('')đường dẫn sau xml, gây ra hiện tượng ghép nối
barlop

Trong Bước 4, thực hiện bất kỳ thao tác chuỗi nào sẽ sử dụng phần tử gói được chỉ định trống ('') cho trường hợp này.
vCillusion

1
Đối với bất cứ ai thắc mắc về điểm 4 và tại sao <Tên> biến mất. Đó là bởi vì sau khi ghép Tên với dấu phẩy không còn cột mà chỉ là giá trị, vì vậy SQL Server không biết nên sử dụng tên nào cho thẻ xml. Ví dụ: truy vấn SELECT 'a' FROM some_table FOR XML PATH('')này sẽ tạo ra : 'aaaaaaa'. Nhưng nếu tên cột sẽ được chỉ định: SELECT 'a' AS Col FROM some_table FOR XML PATH('')bạn nhận được kết quả:<Col>a</Col><Col>a</Col><Col>a</Col>
anth

23

Có một chức năng rất mới trong Azure SQL Database và SQL Server (bắt đầu từ năm 2017) để xử lý tình huống chính xác này. Tôi tin rằng điều này sẽ phục vụ như một phương thức chính thức riêng cho những gì bạn đang cố gắng thực hiện với phương thức XML / STUFF. Thí dụ:

select id, STRING_AGG(name, ',') as abc
from temp1
group by id

STRING_AGG - https://msdn.microsoft.com/en-us/l Library / mt790580.aspx

EDIT: Khi tôi đăng bài này, tôi đã đề cập đến SQL Server 2016 vì tôi nghĩ rằng tôi đã thấy điều đó trên một tính năng tiềm năng sẽ được đưa vào. Hoặc là tôi nhớ rằng không chính xác hoặc một cái gì đó đã thay đổi, cảm ơn vì đã chỉnh sửa đề xuất sửa phiên bản. Ngoài ra, khá ấn tượng và không nhận thức đầy đủ về quy trình đánh giá nhiều bước vừa đưa tôi vào một lựa chọn cuối cùng.


3
STRING_AGG không có trong SQL Server 2016. Nó được cho là sẽ xuất hiện trong "vNext".
N8allan

Rất tiếc, tôi không có ý định ghi đè chỉnh sửa từ @lostmylogin xin lỗi về điều đó ... Đó là người thực sự đã đẩy qua chỉnh sửa chỉnh sửa.
Brian Jorden

5

Trong for xml path, nếu chúng tôi xác định bất kỳ giá trị nào như thế [ for xml path('ENVLOPE') ]thì các thẻ này sẽ được thêm vào với mỗi hàng:

<ENVLOPE>
</ENVLOPE>

2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Ở đây, hàm STUFF truy vấn ở trên được sử dụng để chỉ xóa dấu phẩy đầu tiên (,)khỏi chuỗi xml được tạo (,aaa,bbb,ccc,ddd,eee)sau đó nó sẽ trở thành (aaa,bbb,ccc,ddd,eee).

FOR XML PATH('')chỉ đơn giản là chuyển đổi dữ liệu cột thành (,aaa,bbb,ccc,ddd,eee)chuỗi nhưng trong PATH chúng ta sẽ truyền '' nên nó sẽ không tạo thẻ XML.

Và cuối cùng, chúng tôi đã nhóm các bản ghi bằng cột ID .


2

Tôi đã gỡ lỗi và cuối cùng trả lại truy vấn 'nhồi' của tôi cho nó theo cách thông thường.

Đơn giản

select * from myTable for xml path('myTable')

cung cấp cho tôi nội dung của bảng để ghi vào bảng nhật ký từ trình kích hoạt tôi gỡ lỗi.


1
Declare @Temp As Table (Id Int,Name Varchar(100))
Insert Into @Temp values(1,'A'),(1,'B'),(1,'C'),(2,'D'),(2,'E'),(3,'F'),(3,'G'),(3,'H'),(4,'I'),(5,'J'),(5,'K')
Select X.ID,
stuff((Select ','+ Z.Name from @Temp Z Where X.Id =Z.Id For XML Path('')),1,1,'')
from @Temp X
Group by X.ID

-1

STUFF ((CHỌN khác biệt ',' + CAST (T.ID) TỪ Bảng T trong đó T.ID = 1 CHO XML PATH ('')), 1,1, '') Tên AS


-3

Tôi thường xuyên sử dụng với mệnh đề where

SELECT 
TapuAda=STUFF(( 
SELECT ','+TBL.TapuAda FROM (
SELECT TapuAda FROM T_GayrimenkulDetay AS GD 
INNER JOIN dbo.T_AktiviteGayrimenkul AS AG ON  D.GayrimenkulID=AG.GayrimenkulID WHERE 
AG.AktiviteID=262
) AS TBL FOR XML PATH ('')
),1,1,'')

2
Tôi không thấy đây là một câu trả lời như thế nào, bạn có thể đưa ra một số lời giải thích không?
Gar
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.