Sql Server'da Stuff ve 'For Xml Path' nasıl çalışır


367

Tablo:

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

Gerekli çıktı:

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

Sorgu:

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

Bu sorgu düzgün çalışıyor. Ama bunun nasıl çalıştığını ya da bunu yapmanın başka ya da kısa bir yolu olduğunu açıklamaya ihtiyacım var.

Bunu anlamak için kafam çok karışıyor.



1
Bunun gerçek hayatta çalıştığını görmek için bir SqlFiddle sayfası yaptım . Umarım başkalarına yardımcı olur.
Sabuncu

1
^ Belki de IDfarklı varlıkların farklı bir tablosunda benzersizdir ve bu tablo onlara ait olan şeyleri saklamaktadır.
Nick Rolando

Bazı satırlar farklı bir kimliğe sahipse bu sorgu çalışmaz. Örneğin, 'ddd' ve 'eee' id 2'ye
sahipse

10
Nerede yanlış yaptığımı görmek için bu sayfayı aylık ziyaretim zamanı.
Taylor Ackley

Yanıtlar:


683

Nasıl çalışır:

1. FOR XML ile XML öğesi dizesi alın

Sorgunun sonuna FOR XML PATH eklenmesi, sorgunun sonuçlarını PATH bağımsız değişkeninde bulunan öğe adıyla XML öğeleri olarak çıkarmanıza olanak tanır. Örneğin, aşağıdaki ifadeyi çalıştırırsak:

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

Boş bir dize (FOR XML PATH ('')) ileterek bunun yerine şunu elde ederiz:

,aaa,bbb,ccc,ddd,eee

2. STUFF ile önde gelen virgülleri kaldırın

STUFF deyimi tam olarak bir dizeyi diğerine "doldurur" ve ilk dizenin içindeki karakterlerin yerini alır.

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

Parametreleri STUFF:

  • "Doldurulacak" dize (bizim durumumuzda önde gelen virgül içeren tam ad listesi)
  • Karakterleri silmeye ve eklemeye başlanacak yer (1, boş bir dizeye dolduruyoruz)
  • Silinecek karakter sayısı (1, önde gelen virgül)

Sonuç olarak:

aaa,bbb,ccc,ddd,eee

3. Tam listeyi almak için kimliğe katılın

Daha sonra, adlı kimliklerin bir listesini almak için temp tablosundaki id listesinde buna katılırız:

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;

Ve sonucumuz var:

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

Bu yardımcı olur umarım!


57
Microsoft'un belge ekibinde (varsa) çalışmalısınız
Fandango68

55
@ Fandango68, @ FutbolFan - Microsoft'un belge ekibinde çalışamaz. Açıklamaları çok açık ve çok doğrudan. ;-)
Chris

1
@ChrisProsser katılıyorum. Oracle, LISTAGGOracle 11gR2'de işlev tanıtarak bu konuda Microsoft'un önünde yer almıştır. Bunun yerine kullanmak zorunda olduğum günlerde bu işlevselliği kaçırıyorum. techonthenet.com/oracle/functions/listagg.php
FutbolFan

2
Merhaba. 1. adımda şunları yaparsanız: XML PATH ('') İÇİN temp1'den ad seçin ... <name>aaa</name> <name> bbb </name> ... etc ... yapmadım t Bunu ilk başta gerçekleştirin ... SELECT '' + name ... etc ... olarak değiştirmek etiketleri kaldırır.
KevinVictor

1
@ChrisProsser - Sybase ASA'nın listonlarca yıldır bir işlevi var. Ne yazık ki Microsoft, SQLServer'ı Sybase'in ASE'sine dayandırdı ve geçen yıla kadar hiçbir zaman liste işleviyle uğraşmadı. Kabul ediyorum - bu kafa karıştırıcı. Ve sonra yaparlar, derler string_agg. listOldukça açık olduğunu düşünürdüm .
youcantryreachingme

75

Bu makalede , kodunuzun birleştirilmiş değerleri XML olarak kodlamayan gelişmiş bir sürümü de dahil olmak üzere SQL'de dizeleri birleştirmenin çeşitli yolları ele alınmaktadır.

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

Neler olduğunu anlamak için iç sorgu ile başlayın:

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

Çünkü siz FOR XML , tüm satırları temsil eden bir XML parçası içeren tek bir satır alırsınız.

İlk sütun için bir sütun takma adı belirtmediğiniz için, her satır, öğesinden sonra köşeli parantez içinde belirtilen adla bir XML öğesine sarılır FOR XML PATH. Örneğin FOR XML PATH ('X'), sahip olsaydınız, şuna benzeyen bir XML belgesi alırsınız:

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

Ancak, bir öğe adı belirtmediğiniz için, sadece bir değer listesi alırsınız:

,aaa,bbb,...

.value('.', 'varchar(max)')Sadece herhangi bir "özel" karakteri XML kodlayan olmadan elde edilen bir XML fragmanından değer alır. Artık şuna benzeyen bir dizeniz var:

',aaa,bbb,...'

Daha STUFFsonra işlev, önde gelen virgülü kaldırır ve size aşağıdaki gibi bir sonuç verir:

'aaa,bbb,...'

İlk bakışta oldukça kafa karıştırıcı görünüyor, ancak diğer seçeneklerden bazılarına kıyasla oldukça iyi performans gösterme eğilimi gösteriyor.


2
Sorgunuzda Tür'ün kullanımı nedir. Ben bunu tanımlamak için düşünüyorum, XML yolu sonucu değer depoda olacak (yanlış ise açıklamak emin değilim).
Puneet Chawla

8
@PuneetChawla: yönergesi SQL kullanarak veri döndürmek için söyler türü. Bu olmadan, veriler bir . Burada, sütunda özel karakterler varsa XML kodlaması sorunlarını önlemek için kullanılır . TYPExmlnvarchar(max)name
Richard Deeming

2
@barlop: As SimpleTalk makale açıklar düşürürseniz, TYPEve .value('.', 'varchar(max)'), o zaman sonuç XML kodlu varlıklarla ile sona erebilir.
Richard Deeming

1
@RichardDeeming Verilerin açılı parantez içerip içermediğini mi kastediyorsunuz?
barlop

1
Ancak, bir öğe adı belirtmediğiniz için, sadece bir değerler listesi alırsınız , bu benim eksik olduğum içgörüdür. Teşekkür ederim.
Adam

44

PATH modu, SELECT sorgusundan XML oluştururken kullanılır

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>

Çıktı, sonuç satır kümesindeki her sütun değerinin bir satır öğesine sarıldığı öğe merkezli XML'dir. SELECT yan tümcesi sütun adları için herhangi bir diğer ad belirtmediğinden, oluşturulan alt öğe adları SELECT yan tümcesindeki karşılık gelen sütun adlarıyla aynıdır.

Satır kümesindeki her satır için bir etiket eklenir.

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>

Adım 2 için: Sıfır uzunluklu bir dize belirtirseniz, sarma öğesi üretilmez.

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

4. Adımda değerleri birleştiriyoruz.

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

6. Adımda tarihi kimliğe göre gruplandırıyoruz.

STUFF (source_string, start, length, add_string) Parametreler veya Bağımsız Değişkenler source_string Değiştirilecek kaynak dize. start Uzunluk karakterlerini silmek ve sonra add_string eklemek için source_string içindeki konum. length source_string öğesinden silinecek karakter sayısı. add_string Başlangıç ​​konumunda source_string'e eklenecek karakter dizisi.

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
"Adım 4'te değerleri birleştiriyoruz." Ancak ',', ('')xml yolundan sonra sütun olarak belirtilen neden / nasıl birleştirme işleminin gerçekleşmesine neden olduğu açık değildir
barlop

Adım 4'te, herhangi bir dize işlemi yapmak, bu durumda boş ('') olan belirtilen kaydırma öğesini kullanacaktır.
vCillusion

1
4. maddeyi ve <İsim> 'in neden kaybolduğunu merak eden herkes için. Çünkü virgül ile birleştirme adı artık sütun değil, sadece değer var, bu nedenle SQL Server xml etiketi için hangi adın kullanılması gerektiğini bilmiyor. Örneğin bu sorgu SELECT 'a' FROM some_table FOR XML PATH('')üretecek: 'aaaaaaa'. Ancak sütun adı belirtilecekse: SELECT 'a' AS Col FROM some_table FOR XML PATH('')sonuç elde edersiniz:<Col>a</Col><Col>a</Col><Col>a</Col>
anth

23

Azure SQL Veritabanı ve SQL Server'da (2017'den başlayarak) bu senaryoyu işlemek için çok yeni işlevler var. Bunun XML / STUFF yöntemiyle gerçekleştirmeye çalıştığınız şey için yerel bir resmi yöntem olarak hizmet edeceğine inanıyorum. Misal:

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

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

DÜZENLEME: İlk başta bunu gönderdiğimde, dahil edilecek potansiyel bir özellik üzerinde gördüğümü düşündüğümden SQL Server 2016'dan bahsetmiştim. Ya yanlış hatırladım ya da bir şey değişti, önerilen düzeltmeyi düzelttiğiniz için teşekkürler. Ayrıca, oldukça etkilendim ve sadece son bir seçenek için beni çekti çok adımlı inceleme sürecinin tam olarak farkında değildi.


3
STRING_AGG, SQL Server 2016'da değil. "VNext" dilinde geldiği söyleniyor.
N8allan

Üzgünüz, @lostmylogin'deki düzenlemenin üzerine yazmaktan bahsetmedim, bunun için üzgünüm ... Aslında düzeltme düzenlemesini iten de bu.
Brian Jorden

5

İçinde for xml path, böyle bir değer tanımlarsak, [ for xml path('ENVLOPE') ]bu etiketler her satıra eklenir:

<ENVLOPE>
</ENVLOPE>

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

Burada yukarıdaki sorguda STUFF işlevi, sadece (,)oluşturulan xml dizesinden ilk virgül kaldırmak için kullanılır, (,aaa,bbb,ccc,ddd,eee)sonra olur (aaa,bbb,ccc,ddd,eee).

Ve FOR XML PATH('')sadece sütun verilerini (,aaa,bbb,ccc,ddd,eee)dizeye dönüştürür, ancak PATH'de '' geçiriyoruz , böylece bir XML etiketi oluşturmayacak.

Sonunda kayıtları ID sütununu kullanarak grupladık .


2

Hata ayıklama yaptım ve sonunda benim 'doldurulmuş' sorguyu normal şekilde geri döndü.

basitçe

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

hata ayıklama tetik bir günlük tabloya yazmak için bana tablo içeriğini verir.


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 ((SELECT farklı ',' + CAST (T.ID) Tablo T'den TID = 1 XML YOLU ('') İÇİN), 1,1, '') AS Ad


-3

Sıklıkla nerede fıkra ile kullanıyorum

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
Bunun nasıl bir cevap olduğunu görmüyorum, lütfen bazı açıklamalara atılabilir misiniz?
Gar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.