Como as coisas e 'For Xml Path' funcionam no Sql Server

367

A tabela é:

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

Saída necessária:

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

Inquerir:

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

Esta consulta está funcionando corretamente. Mas eu só preciso da explicação de como isso funciona ou se existe alguma outra maneira, ou curta, de fazer isso.

Estou ficando muito confuso para entender isso.

Puneet Chawla
fonte
11
Eu fiz uma página do SqlFiddle para isso, para vê-la funcionando na vida real. Espero que ajude os outros.
Sabuncu
11
^ Talvez o IDseja único em uma tabela diferente de entidades diferentes, e esta tabela esteja armazenando coisas que pertencem a elas.
Nick Rolando
Esta consulta não funciona se algumas das linhas tiverem um ID diferente. por exemplo, se 'ddd' e 'eee' tiverem o número 2. #
KevinVictor 19/10/2018
10
Hora da minha visita mensal a esta página para ver onde errei.
Taylor Ackley

Respostas:

683

Aqui está como funciona:

1. Obtenha a string do elemento XML com FOR XML

A adição de FOR XML PATH ao final de uma consulta permite gerar os resultados da consulta como elementos XML, com o nome do elemento contido no argumento PATH. Por exemplo, se formos executar a seguinte instrução:

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

Ao passar uma string em branco (FOR XML PATH ('')), obtemos o seguinte:

,aaa,bbb,ccc,ddd,eee

2. Remova a vírgula inicial com STUFF

A instrução STUFF literalmente "enche" uma string em outra, substituindo caracteres na primeira string. No entanto, estamos usando-a simplesmente para remover o primeiro caractere da lista de valores resultante.

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

Os parâmetros de STUFFsão:

  • A string a ser "recheada" (no nosso caso, a lista completa do nome com uma vírgula à esquerda)
  • O local para começar a excluir e inserir caracteres (1, estamos inserindo uma sequência em branco)
  • O número de caracteres a serem excluídos (1, sendo a vírgula inicial)

Então, acabamos com:

aaa,bbb,ccc,ddd,eee

3. Inscreva-se no id para obter a lista completa

Em seguida, juntamos isso à lista de IDs na tabela temporária, para obter uma lista de IDs com o nome:

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;

E nós temos o nosso resultado:

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

Espero que isto ajude!

FutbolFan
fonte
57
Você deve trabalhar para a equipe de documentação da Microsoft (se houver)
Fandango68
55
@ Fandango68, @ FutbolFan - Ele não pode trabalhar para a equipe de documentação da Microsoft. Suas explicações são muito claras e diretas demais. ;-)
Chris
11
@ChrisProsser Eu concordo. A Oracle está à frente da Microsoft ao introduzir a LISTAGGfunção no Oracle 11gR2. Sinto falta dessa funcionalidade nos dias em que tenho que usá-la. techonthenet.com/oracle/functions/listagg.php
FutbolFan
2
Olá. Na etapa 1, se você fizer: SELECT name FROM temp1 FOR XML PATH ('') ... você obtém <name>aaa</name> <name> bbb </name> ... etc ... eu não ' para perceber isso no início ... Alterá-lo para SELECT '' + name ... etc ... remove as tags.
precisa saber é o seguinte
11
@ ChrisProsser - A Sybase ASA tem uma listfunção há décadas. Infelizmente, a Microsoft baseou o SQLServer no ASE da Sybase e nunca se incomodou com uma função de lista até o ano passado. Eu concordo - é incompreensível. E então eles chamam string_agg. Eu teria pensado que listera bastante óbvio.
Youcantryreachingme
75

Este artigo aborda várias maneiras de concatenar seqüências de caracteres no SQL, incluindo uma versão aprimorada do seu código que não codifica XML os valores concatenados.

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

Para entender o que está acontecendo, comece com a consulta interna:

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

Como você está especificando FOR XML, você obterá uma única linha contendo um fragmento XML que representa todas as linhas.

Como você não especificou um alias de coluna para a primeira coluna, cada linha seria quebrada em um elemento XML com o nome especificado entre colchetes após o FOR XML PATH. Por exemplo, se você tivesse FOR XML PATH ('X'), obteria um documento XML parecido com:

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

Mas, como você não especificou o nome de um elemento, basta obter uma lista de valores:

,aaa,bbb,...

Ele .value('.', 'varchar(max)')simplesmente recupera o valor do fragmento XML resultante, sem codificar em XML nenhum caractere "especial". Agora você tem uma sequência que se parece com:

',aaa,bbb,...'

A STUFFfunção remove a vírgula inicial, fornecendo um resultado final parecido com:

'aaa,bbb,...'

Parece bastante confuso à primeira vista, mas tende a funcionar muito bem em comparação com algumas das outras opções.

Richard Deeming
fonte
2
Qual é o uso do Type na sua consulta. Eu acho que, para definir, o resultado do caminho XML será armazenado em valor (não tenho certeza, explique se estiver errado).
Puneet Chawla
8
@PuneetChawla: A TYPEdiretiva diz ao SQL para retornar os dados usando o xmltipo Sem ele, os dados são retornados como um nvarchar(max). É usado aqui para evitar problemas de codificação XML se houver caracteres especiais na namecoluna.
Richard Deeming
2
@ barlop: Como o artigo do SimpleTalk explica, se você soltar o TYPEe .value('.', 'varchar(max)'), poderá acabar com entidades codificadas em XML no resultado.
Richard Deeming
11
@RichardDeeming, você quer dizer se os dados contêm ou podem conter colchetes angulares?
barlop
11
Mas, como você não especificou o nome de um elemento, você acabou de obter uma lista de valores . Esse é o insight que estava faltando. Obrigado.
Adam
44

O modo PATH é usado na geração de XML a partir de uma consulta 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>

A Saída é um XML centrado no elemento em que cada valor da coluna no conjunto de linhas resultante é agrupado em um elemento de linha. Como a cláusula SELECT não especifica nenhum alias para os nomes das colunas, os nomes dos elementos filhos gerados são iguais aos nomes das colunas correspondentes na cláusula SELECT.

Para cada linha do conjunto de linhas, uma tag é adicionada.

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>

Para a Etapa 2: se você especificar uma cadeia de comprimento zero, o elemento de quebra automática não será produzido.

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

Na etapa 4, estamos concatenando os valores.

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

Na Etapa 6, estamos agrupando a data por ID.

STUFF (source_string, start, length, add_string) Parâmetros ou argumentos source_string A cadeia de origem a ser modificada. start A posição no source_string para excluir caracteres de comprimento e, em seguida, insira add_string. length O número de caracteres a serem excluídos do source_string. add_string A sequência de caracteres a serem inseridos no source_string na posição inicial.

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
fonte
11
Você escreve "Na etapa 4, estamos concatenando os valores". Mas não é claro por que / como o ','especificado como coluna, combinado com o ('')após caminho xml, faz com que a concatenação de ocorrer
barlop
Na Etapa 4, executar qualquer operação de cadeia de caracteres usará o elemento de quebra automática especificado em branco ('') para este caso.
vCillusion
11
Para quem se pergunta sobre o ponto 4 e por que <Nome> desaparece. Isso ocorre porque, após a concatenação Name com vírgula, não há mais coluna, mas apenas valor; portanto, o SQL Server não sabe qual nome para a tag xml deve ser usado. Por exemplo esta consulta SELECT 'a' FROM some_table FOR XML PATH('')vai produzir: 'aaaaaaa'. Mas se o nome da coluna for especificado: SELECT 'a' AS Col FROM some_table FOR XML PATH('')você obtém resultado:<Col>a</Col><Col>a</Col><Col>a</Col>
anth 18/11/19
23

Há uma funcionalidade muito nova no Banco de Dados SQL do Azure e no SQL Server (a partir de 2017) para lidar com esse cenário exato. Acredito que isso serviria como um método oficial nativo para o que você está tentando realizar com o método XML / STUFF. Exemplo:

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

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

EDIT: Quando publiquei isso originalmente, mencionei o SQL Server 2016, pois achei que vi isso em um recurso em potencial que deveria ser incluído. Lembrei-me que incorretamente ou algo mudou, obrigado pela edição sugerida que corrige a versão. Além disso, fiquei bastante impressionado e não estava totalmente ciente do processo de revisão em várias etapas que me levou a uma opção final.

Brian Jorden
fonte
3
STRING_AGG não está no SQL Server 2016. Diz-se que está chegando no "vNext".
N8allan
Ops, não tive a intenção de substituir a edição do @lostmylogin, desculpe por isso ... Foi quem realmente fez a edição de correção.
18770 Brian Brian
5

Em for xml path, se definirmos qualquer valor como [ for xml path('ENVLOPE') ], essas tags serão adicionadas a cada linha:

<ENVLOPE>
</ENVLOPE>
vikas
fonte
2
SELECT ID, 
    abc = STUFF(
                 (SELECT ',' + name FROM temp1 FOR XML PATH ('')), 1, 1, ''
               ) 
FROM temp1 GROUP BY id

Aqui, na função STUFF da consulta acima, é usada apenas para remover a primeira vírgula (,)da string xml gerada, (,aaa,bbb,ccc,ddd,eee)e ela se tornará(aaa,bbb,ccc,ddd,eee) .

E FOR XML PATH('')simplesmente converte os dados da coluna em (,aaa,bbb,ccc,ddd,eee)string, mas em PATH estamos passando '', para que não crie uma tag XML.

E no final, agrupamos os registros usando a coluna ID .

Mahendra Singh Dhami
fonte
2

Eu fiz a depuração e, finalmente, retornei minha consulta 'recheada' para ela da maneira normal.

Simplesmente

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

fornece o conteúdo da tabela para gravar em uma tabela de log a partir de um gatilho que depuro.

SlavaTT
fonte
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
fonte
-1

STUFF ((SELECT distinto ',' + CAST (T.ID) DA Tabela T, em que T.ID = 1 FOR XML PATH ('')), 1,1, '') AS Name

B.Nishan
fonte
-3

Estou frequentemente usando com cláusula 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
fonte
2
Não vejo como isso é uma resposta, você poderia dar algumas explicações, por favor?
Gar