Как использовать COALESCE с несколькими строками и без предшествующей запятой?

27

Я пытаюсь добиться следующего:

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

К сожалению, я получаю "Лос-Анджелес, Сан-Франциско, Сакраменто, Джексонвилл, Майами"

Я могу достичь желаемых результатов с помощью функции STUFF, но мне было интересно, есть ли более чистый способ сделать это с помощью 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;

Благодарность

user2732180
источник

Ответы:

45

Это может быть более чистый подход, который вы ищете. В основном, проверьте, была ли переменная еще инициализирована. Если это не так, установите пустую строку и добавьте первый город (без запятой). Если это так, добавьте запятую, а затем добавьте город.

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

Конечно, это работает только для заполнения переменной в каждом штате. Если вы вытягиваете список для каждого состояния по одному, есть лучшее решение за один раз:

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];

Полученные результаты:

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

Для заказа по названию города в каждом штате:

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];

В базе данных SQL Azure или SQL Server 2017+ вы можете использовать новую STRING_AGG()функцию :

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

И упорядочено по названию города:

SELECT [state], cities = STRING_AGG(city, N', ') 
                         WITHIN GROUP (ORDER BY city)
  FROM dbo.tbl
  GROUP BY [state]
  ORDER BY [state];
Аарон Бертран
источник
Спасибо Аарон. Мое текущее решение почти идентично вашему, за исключением того, что я использую DISTINCT вместо GROUP BY.
user2732180
2
@ user2732180 Вы должны использовать GROUP BY, так как он с большей вероятностью выполнит объединение один раз для каждого состояния. С DISTINCT он будет применять одну и ту же конкатенацию для каждого экземпляра Калифорнии, например, и только потом отбрасывать всю работу, которую он сделал, создавая эти дубликаты.
Аарон Бертран
6

Просто чтобы добавить ответ Аарона выше ...

Имейте в ORDER BYвиду, что может сломаться, только включив последний элемент в ваш запрос. В моем случае я не группировался, поэтому не уверен, что это имеет значение. Я использую SQL 2014. В моем случае у меня есть что-то вроде value1, value2, value3 ... но мой результат в переменной был только value3.


Аарон прокомментировал, чтобы сказать:

Об этом сообщалось как минимум четыре раза в Connect:

  1. В результате конкатенации переменных и упорядочения по фильтрам (например, где условие)
  2. (n) сборка varchar из ResultSet завершается сбоем при добавлении ORDER BY
  3. Присвоение локальной переменной из упорядоченного SELECT с помощью CROSS APPLY и табличной функции возвращает только последнее значение
  4. При объединении значений varchar (max) / nvarchar (max) из табличной переменной неправильные результаты могут быть возвращены при фильтрации и упорядочении по столбцу без первичного ключа

Пример ответа от Microsoft:

Поведение, которое вы видите, является дизайном. Использование операций присваивания (в данном примере конкатенации) в запросах с предложением ORDER BY имеет неопределенное поведение.

Ответ также ссылается на KB 287515:

PRB: план выполнения и результаты агрегированных конкатенационных запросов зависят от местоположения выражения

Решение состоит в том, чтобы использовать FOR XML PATH(второй подход в ответе Аарона), если важен порядок конкатенации, и, конечно, если вы хотите быть уверены, что включили все значения. Также см:

Конкатенация nvarchar / index / nvarchar (max) необъяснимое поведение при переполнении стека

ebol2000
источник