Como usar COALESCE com várias linhas e sem vírgula anterior?

27

Estou tentando conseguir o seguinte:

California | Los Angeles, San Francisco, Sacramento
Florida    | Jacksonville, Miami

Infelizmente, estou recebendo ", Los Angeles, São Francisco, Sacramento, Jacksonville, Miami"

Posso alcançar os resultados desejados usando a função STUFF, mas fiquei imaginando se há uma maneira mais limpa de fazer isso usando COALESCE?

STATE       | CITY
California  | San Francisco
California  | Los Angeles
California  | Sacramento
Florida     | Miami
Florida     | Jacksonville 


DECLARE @col NVARCHAR(MAX);
SELECT @col= COALESCE(@col, '') + ',' + city
FROM tbl where city = 'California';
SELECT @col;

obrigado

user2732180
fonte

Respostas:

45

Essa pode ser a abordagem mais limpa que você procura. Basicamente, verifique se a variável já foi inicializada. Caso contrário, defina-o como a sequência vazia e acrescente a primeira cidade (sem vírgula à esquerda). Se houver, adicione uma vírgula e depois a cidade.

DECLARE @col nvarchar(MAX);
SELECT @col = COALESCE(@col + ',', '') + city
  FROM dbo.tbl WHERE state = 'California';

Obviamente, isso só funciona para preencher uma variável por estado. Se você está puxando a lista de cada estado, um de cada vez, há uma solução melhor de uma só vez:

SELECT [state], cities = STUFF((
    SELECT N', ' + city FROM dbo.tbl
    WHERE [state] = x.[state]
    FOR XML PATH(''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 2, N'')
FROM dbo.tbl AS x
GROUP BY [state]
ORDER BY [state];

Resultados:

state       cities
----------  --------------------------------------
California  San Francisco, Los Angeles, Sacramento  
Florida     Miami, Jacksonville

Para solicitar pelo nome da cidade em cada estado:

SELECT [state], cities = STUFF((
    SELECT N', ' + city FROM dbo.tbl
    WHERE [state] = x.[state]
    ORDER BY city
    FOR XML PATH(''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 2, N'')
FROM dbo.tbl AS x
GROUP BY [state]
ORDER BY [state];

No Banco de Dados SQL do Azure ou no SQL Server 2017 ou superior, você pode usar a nova STRING_AGG()função :

SELECT [state], cities = STRING_AGG(city, N', ')
  FROM dbo.tbl
  GROUP BY [state]
  ORDER BY [state];

E ordenado pelo nome da cidade:

SELECT [state], cities = STRING_AGG(city, N', ') 
                         WITHIN GROUP (ORDER BY city)
  FROM dbo.tbl
  GROUP BY [state]
  ORDER BY [state];
Aaron Bertrand
fonte
Obrigado Aaron. Minha solução atual é quase idêntica à sua, exceto que estou usando DISTINCT em vez de GROUP BY.
user2732180
2
@ user2732180 Você deve usar um GROUP BY, pois é mais provável que a concatenação seja executada uma vez por estado. Com o DISTINCT, ele aplicará a mesma concatenação para todas as instâncias da Califórnia, por exemplo, e só então descartará todo o trabalho que ele fez para gerar essas duplicatas.
Aaron Bertrand
6

Apenas para adicionar à resposta de Aaron acima ...

Esteja ciente de que um ORDER BYpode ser interrompido incluindo apenas o último item na sua consulta. No meu caso, eu não estava agrupando, então não tenho certeza se isso faz alguma diferença. Estou usando o SQL 2014. No meu caso, tenho algo como valor1, valor2, valor3 ... mas meu resultado na variável foi apenas valor3.


Aaron comentou ao dizer:

Isso foi relatado pelo menos quatro vezes no Connect:

  1. Em concatenação variável e ordem por resultados de filtros (como a condição de onde)
  2. (n) a criação de varchar do ResultSet falha quando ORDER BY é adicionado
  3. Atribuir uma variável local de um SELECT ordenado com CROSS APPLYs e uma função com valor de tabela retorna apenas o último valor
  4. Ao concatenar os valores varchar (max) / nvarchar (max) de uma variável da tabela, resultados incorretos podem ser retornados se filtrar e ordenar por uma coluna de chave não primária

Exemplo de resposta da Microsoft:

O comportamento que você está vendo é por design. O uso de operações de atribuição (concatenação neste exemplo) em consultas com a cláusula ORDER BY tem um comportamento indefinido.

A resposta também faz referência a KB 287515:

PRB: Plano de execução e resultados de consultas agregadas de concatenação dependem do local da expressão

A solução é usar FOR XML PATH(a segunda abordagem na resposta de Aaron) se a ordem da concatenação é importante e, é claro, se você quiser ter certeza de incluir todos os valores. Veja também:

comportamento inexplicável da concatenação nvarchar / index / nvarchar (max) no estouro de pilha

ebol2000
fonte