Jak działają rzeczy i „Ścieżka Xml” na serwerze Sql

367

Tabela jest:

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

Wymagana moc wyjściowa:

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

Pytanie:

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

To zapytanie działa poprawnie. Potrzebuję tylko wyjaśnienia, jak to działa, czy jest jakiś inny lub krótki sposób, aby to zrobić.

Jestem bardzo zdezorientowany, aby to zrozumieć.

Puneet Chawla
źródło
1
Zrobiłem dla tego stronę SqlFiddle , aby zobaczyć, jak działa w prawdziwym życiu. Mam nadzieję, że pomaga innym.
Sabuncu
1
^ Być może IDjest unikalny w innej tabeli różnych podmiotów, a ta tabela przechowuje rzeczy, które do nich należą.
Nick Rolando,
To zapytanie nie działa, jeśli niektóre wiersze mają inny identyfikator. np. jeśli „ddd” i „eee” mają Id 2.
KevinVictor,
10
Czas na moją comiesięczną wizytę na tej stronie, aby sprawdzić, gdzie popełniłem błąd.
Taylor Ackley,

Odpowiedzi:

683

Oto jak to działa:

1. Pobierz ciąg elementu XML za pomocą FOR XML

Dodanie ŚCIEŻKI XML na końcu zapytania umożliwia wyświetlenie wyników zapytania w postaci elementów XML z nazwą elementu zawartą w argumencie ŚCIEŻKA. Na przykład, jeśli mielibyśmy uruchomić następującą instrukcję:

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

Przekazując pusty ciąg (FOR XML PATH ('')), otrzymujemy zamiast tego:

,aaa,bbb,ccc,ddd,eee

2. Usuń przecinek wiodący za pomocą STUFF

Instrukcja STUFF dosłownie „upycha” jeden ciąg na inny, zastępując znaki w pierwszym ciągu, jednak używamy go po prostu do usunięcia pierwszego znaku z wynikowej listy wartości.

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

Parametry STUFFto:

  • Ciąg do „wypchania” (w naszym przypadku pełna lista nazw z wiodącym przecinkiem)
  • Miejsce, w którym należy rozpocząć usuwanie i wstawianie znaków (1, upychamy w pusty ciąg)
  • Liczba znaków do usunięcia (1, będący wiodącym przecinkiem)

W rezultacie otrzymujemy:

aaa,bbb,ccc,ddd,eee

3. Dołącz do id, aby uzyskać pełną listę

Następnie po prostu dołączamy do tego na liście identyfikatorów w tabeli tymczasowej, aby uzyskać listę identyfikatorów o nazwie:

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;

I mamy nasz wynik:

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

Mam nadzieję że to pomoże!

FutbolFan
źródło
57
Powinieneś pracować dla zespołu dokumentacji Microsoftu (jeśli istnieje)
Fandango68
55
@ Fandango68, @ FutbolFan - Nie może pracować dla zespołu dokumentacji Microsoftu. Jego wyjaśnienia są zbyt jasne i zbyt bezpośrednie. ;-)
Chris,
1
@ChrisProsser Zgadzam się. Oracle wyprzedza Microsoft w tym zakresie, wprowadzając LISTAGGfunkcję w Oracle 11gR2. Tęsknię za tą funkcjonalnością w dniach, w których muszę z niej korzystać. techonthenet.com/oracle/functions/listagg.php
FutbolFan
2
Witaj. W kroku 1, jeśli wykonasz: WYBIERZ nazwę Z temp1 DLA ŚCIEŻKI XML („”) ... otrzymujesz <nazwa>aaa</nazwa> <nazwa> bbb </nazwa> ... itd. ... nie zrobiłam najpierw zdaj sobie z tego sprawę ... Zmiana na WYBIERZ + nazwa ... itd. usuwa tagi.
KevinVictor,
1
@ChrisProsser - Sybase ASA listdziała od dziesięcioleci. Niestety zamiast tego SQLServer firmy Microsoft oparty na ASE Sybase, i nigdy nie przejmował się funkcją listy aż do zeszłego roku. Zgadzam się - to zadziwiające. A potem to robią, nazywają to string_agg. Myślałem, że listto całkiem oczywiste.
youcantryreachingme
75

W tym artykule omówiono różne sposoby konkatenacji ciągów SQL, w tym ulepszoną wersję kodu, który nie koduje XML skonkatenowanych wartości.

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

Aby zrozumieć, co się dzieje, zacznij od wewnętrznego zapytania:

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

Ponieważ określasz FOR XML, otrzymasz pojedynczy wiersz zawierający fragment XML reprezentujący wszystkie wiersze.

Ponieważ nie określono aliasu kolumny dla pierwszej kolumny, każdy wiersz zostałby zawinięty w element XML o nazwie podanej w nawiasach po znaku FOR XML PATH. Na przykład, gdybyś miał FOR XML PATH ('X'), dostaniesz dokument XML, który wyglądałby tak:

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

Ponieważ jednak nie podałeś nazwy elementu, otrzymujesz listę wartości:

,aaa,bbb,...

Po .value('.', 'varchar(max)')prostu pobiera wartość z wynikowego fragmentu XML, bez kodowania XML żadnych „znaków specjalnych”. Masz teraz ciąg, który wygląda następująco:

',aaa,bbb,...'

Następnie STUFFfunkcja usuwa wiodący przecinek, dając końcowy wynik, który wygląda następująco:

'aaa,bbb,...'

Na pierwszy rzut oka wygląda dość myląco, ale zwykle działa całkiem dobrze w porównaniu do niektórych innych opcji.

Richard Deeming
źródło
2
Do czego służy Type w zapytaniu. Myślę, że do zdefiniowania, wynik ścieżki XML będzie przechowywany w wartości (nie wiem, wyjaśnij to, jeśli jest źle).
Puneet Chawla
8
@PuneetChawla: dyrektywa mówi SQL, aby powrócić do danych przy użyciu typu. Bez tego dane są zwracane jako . Służy tutaj, aby uniknąć problemów z kodowaniem XML, jeśli w kolumnie znajdują się znaki specjalne . TYPExmlnvarchar(max)name
Richard Deeming,
2
@barlop: Jak wyjaśnia artykuł SimpleTalk , jeśli upuścisz TYPEi .value('.', 'varchar(max)'), możesz w rezultacie uzyskać encje zakodowane w XML.
Richard Deeming
1
@RichardDeeming masz na myśli to, czy dane zawierają nawiasy kątowe, czy mogą?
barlop
1
Ale ponieważ nie określiłeś nazwy elementu, otrzymujesz listę wartości , to jest wgląd, którego mi brakowało. Dziękuję Ci.
Adam
44

Tryb PATH służy do generowania XML z zapytania 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>

Dane wyjściowe to XML zorientowany na elementy, w którym każda wartość kolumny w wynikowym zestawie wierszy jest zawinięta w element wiersza. Ponieważ klauzula SELECT nie określa aliasów dla nazw kolumn, wygenerowane nazwy elementów potomnych są takie same jak odpowiadające im nazwy kolumn w klauzuli SELECT.

Do każdego wiersza w zestawie wierszy dodawany jest znacznik.

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>

W kroku 2: Jeśli określisz ciąg o zerowej długości, element owijający nie zostanie utworzony.

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

W kroku 4 łączymy wartości.

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

W kroku 6 grupujemy datę według identyfikatora.

STUFF (ciąg_źródłowy, początek, długość, ciąg_dodany) Parametry lub argumenty ciąg_źródłowy Ciąg źródłowy do modyfikacji. start Pozycja w łańcuchu_źródłowym, aby usunąć znaki długości, a następnie wstawić łańcuch_odpowiedzi. długość Liczba znaków do usunięcia z łańcucha_źródłowego. add_string Sekwencja znaków do wstawienia do łańcucha_źródłowego w pozycji początkowej.

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 |
-----------------------------------
Neha Chopra
źródło
1
Piszesz „W kroku 4 łączymy wartości”. Ale nie jest jasne, dlaczego / w jaki sposób ','podana jako kolumna w połączeniu ze ('')ścieżką po xml powoduje konkatenację
barlop 12.04.18
W kroku 4 wykonanie dowolnej operacji na łańcuchach spowoduje użycie określonego elementu zawijania, który w tym przypadku jest pusty („”).
vCillusion
1
Dla każdego, kto zastanawia się nad punktem 4 i dlaczego <Name> znika. Jest tak, ponieważ po konkatenacji Nazwa z przecinkiem nie ma już kolumny, a jedynie wartość, więc SQL Server nie wie, jakiej nazwy należy użyć. Na przykład ta kwerenda SELECT 'a' FROM some_table FOR XML PATH('')będzie produkować: 'aaaaaaa'. Ale jeśli nazwa kolumny zostaną określone: SELECT 'a' AS Col FROM some_table FOR XML PATH('')uzyskać wynik:<Col>a</Col><Col>a</Col><Col>a</Col>
Anth
23

Dostępna jest bardzo nowa funkcjonalność w bazie danych SQL Azure i SQL Server (począwszy od 2017 r.) Do obsługi tego dokładnie scenariusza. Wierzę, że posłuży to jako natywna oficjalna metoda tego, co próbujesz osiągnąć za pomocą metody XML / STUFF. Przykład:

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

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

EDYCJA: Kiedy pierwotnie to opublikowałem, wspomniałem o SQL Server 2016, ponieważ myślałem, że widziałem to w potencjalnej funkcji, która miała zostać uwzględniona. Albo zapamiętałem to niepoprawnie, albo coś się zmieniło, dziękuję za sugerowaną edycję naprawiającą wersję. Poza tym byłem pod wrażeniem i nie byłem w pełni świadomy wieloetapowego procesu recenzji, który właśnie przyciągnął mnie do ostatecznej opcji.

Brian Jorden
źródło
3
STRING_AGG nie ma w SQL Server 2016. Mówi się, że będzie w „vNext”.
N8allan,
Ups, nie chciałem nadpisać edycji z @lostmylogin, przepraszam za to ... To właśnie on przeforsował edycję korekty.
Brian Jorden,
5

W for xml path, jeśli zdefiniujemy dowolną wartość, [ for xml path('ENVLOPE') ]wówczas te tagi zostaną dodane do każdego wiersza:

<ENVLOPE>
</ENVLOPE>
Vikas
źródło
2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Tutaj w powyższym zapytaniu funkcja STUFF służy do usunięcia pierwszego przecinka (,)z wygenerowanego ciągu xml, (,aaa,bbb,ccc,ddd,eee)po czym stanie się (aaa,bbb,ccc,ddd,eee).

I FOR XML PATH('')po prostu konwertuje dane kolumny na (,aaa,bbb,ccc,ddd,eee)ciąg, ale w PATH mijamy '', więc nie utworzy tagu XML.

Na koniec pogrupowaliśmy rekordy za pomocą kolumny ID .

Mahendra Singh Dhami
źródło
2

Debugowałem i w końcu zwróciłem moje „wypchane” zapytanie w normalny sposób.

Po prostu

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

daje mi zawartość tabeli do zapisania w tabeli dziennika z wyzwalacza, który debuguję.

SlavaTT
źródło
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
Omkar Naik
źródło
-1

STUFF ((WYBIERZ odrębny ',' + CAST (T.ID) FROM tabeli T, gdzie T.ID = 1 DLA ŚCIEŻKI XML ('')), 1,1, '') AS Nazwa

B.Nishan
źródło
-3

Często używam z klauzulą ​​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,'')
sbaysal
źródło
2
Nie rozumiem, jak to jest odpowiedź. Czy mógłbyś podać jakieś wyjaśnienia?
Gar