STRING_AGG ведет себя не так, как ожидалось - PullRequest
0 голосов
/ 27 сентября 2018

У меня следующий запрос:

WITH cteCountryLanguageMapping AS (
    SELECT * FROM (
        VALUES
            ('Spain', 'English'),
            ('Spain', 'Spanish'),
            ('Sweden', 'English'),
            ('Switzerland', 'English'),
            ('Switzerland', 'French'),
            ('Switzerland', 'German'),
            ('Switzerland', 'Italian')
    ) x ([Country], [Language])
)
SELECT
    [Country],
    CASE COUNT([Language])
        WHEN 1 THEN MAX([Language])
        WHEN 2 THEN STRING_AGG([Language], ' and ')
        ELSE STRING_AGG([Language], ', ')
    END AS [Languages],
    COUNT([Language]) AS [LanguageCount]
FROM cteCountryLanguageMapping
GROUP BY [Country]

Я ожидал, что значение в столбце Языки для Швейцарии будет разделено запятой, т.е.:

  | Country     | Languages                                 | LanguageCount
--+-------------+-------------------------------------------+--------------
1 | Spain       | Spanish and English                       | 2
2 | Sweden      | English                                   | 1
3 | Switzerland | French, German, Italian, English          | 4

Вместо этого я получаю вывод ниже(4 значения разделены and):

  | Country     | Languages                                 | LanguageCount
--+-------------+-------------------------------------------+--------------
1 | Spain       | Spanish and English                       | 2
2 | Sweden      | English                                   | 1
3 | Switzerland | French and German and Italian and English | 4

Чего мне не хватает?


Вот еще один пример:

SELECT y, STRING_AGG(z, '+') AS STRING_AGG_PLUS, STRING_AGG(z, '-') AS STRING_AGG_MINUS
FROM (
    VALUES
        (1, 'a'),
        (1, 'b')
) x (y, z)
GROUP by y

  | y | STRING_AGG_PLUS | STRING_AGG_MINUS
--+---+-----------------+-----------------
1 | 1 | a+b             | a+b

Естьэто ошибка в SQL Server?

Ответы [ 2 ]

0 голосов
/ 11 октября 2018

Не повторяйся *.Вы повторяете себя, используя MAX(...), LIST_AGG(...', ') и LIST_AGG(...' and ').Вы можете просто переписать свой запрос, как этот, и в итоге вы получите более качественный план:

WITH cteCountryLanguageMapping AS (
    SELECT * FROM (
        VALUES
            ('Spain', 'English'),
            ('Spain', 'Spanish'),
            ('Sweden', 'English'),
            ('Switzerland', 'English'),
            ('Switzerland', 'French'),
            ('Switzerland', 'German'),
            ('Switzerland', 'Italian')
    ) x (Country, Language)
), results AS (
    SELECT
        Country,
        COUNT(Language) AS LanguageCount,
        STRING_AGG(Language, ', ') AS Languages
    FROM cteCountryLanguageMapping
    GROUP BY Country
)
SELECT Country, LanguageCount, CASE LanguageCount
    WHEN 2 THEN REPLACE(Languages, ', ', ' and ')
    ELSE Languages
END AS Languages_Fixed
FROM results

Результат:

| Country     | LanguageCount | Languages_Fixed                  |
|-------------|---------------|----------------------------------|
| Spain       | 2             | Spanish and English              |
| Sweden      | 1             | English                          |
| Switzerland | 4             | French, German, Italian, English |

DB Fiddle

* Я не хотел повторять и других, говоря, что это ошибка.

0 голосов
/ 27 сентября 2018

Да, это ошибка (tm), присутствующая (на момент написания) в версиях до SQL Server 2017 CU12 (но, согласно @DanGuzman, в базе данных SQL Azure, но, по-видимому, она уже исправлена ​​и исправление можетземля в соседнем ТС).В частности, часть в оптимизаторе, которая выполняет исключение общих подвыражений (гарантируя, что мы не вычисляем выражения больше, чем необходимо), неправильно считает все выражения формы STRING_AGG(x, <separator>) идентичными, пока совпадает x, независимо от того, что <separator>is, и объединяет их с первым вычисленным выражением в запросе.

Один из обходных путей - убедиться, что x не совпадает, выполнив какое-то (почти) преобразование идентичности для него.Так как мы имеем дело со строками, конкатенация пустой будет делать:

SELECT y, STRING_AGG(z, '+') AS STRING_AGG_PLUS, STRING_AGG('' + z, '-') AS STRING_AGG_MINUS
FROM (
    VALUES
        (1, 'a'),
        (1, 'b')
) x (y, z)
GROUP by y
...