J'ai la requête suivante:
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]
Je m'attendais à ce que la colonne de la colonne Languages de la Suisse soit séparée par des virgules, à savoir:
| Country | Languages | LanguageCount
--+-------------+-------------------------------------------+--------------
1 | Spain | Spanish and English | 2
2 | Sweden | English | 1
3 | Switzerland | French, German, Italian, English | 4
Au lieu de cela, j'obtiens la sortie ci-dessous (les 4 valeurs sont séparées par and
):
| Country | Languages | LanguageCount
--+-------------+-------------------------------------------+--------------
1 | Spain | Spanish and English | 2
2 | Sweden | English | 1
3 | Switzerland | French and German and Italian and English | 4
Qu'est-ce que je rate?
Voici un autre exemple:
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
Est-ce un bug dans SQL Server?
Oui, il s'agit d'un bogue (tm) présent dans les versions (au moment de l'écriture) jusqu'à SQL Server 2017 CU12 (mais pas, selon @DanGuzman, dans Azure SQL Database. Apparemment, il est déjà corrigé et le correctif pourrait atterrir dans le prochain CU). Plus précisément, la partie de l’optimiseur qui effectue l’élimination des sous-expressions courantes (en veillant à ne pas calculer les expressions plus que nécessaire) considère à tort toutes les expressions de la forme STRING_AGG(x, <separator>)
identiques dès lors que x
correspond, quelle que soit la valeur de <separator>
, et les unifie avec la première expression calculée dans la requête.
Une solution de contournement consiste à vous assurer que x
ne correspond pas en effectuant une sorte de transformation de l'identité (ou presque) sur celle-ci. Puisque nous avons affaire à des chaînes, concaténer une chaîne vide fera:
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
Ne te répète pas *. Vous vous répétez en utilisant MAX(...)
, LIST_AGG(...', ')
et LIST_AGG(...' and ')
. Vous pouvez simplement réécrire votre requête comme ceci et vous retrouver avec un meilleur plan:
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
Résultat:
| Country | LanguageCount | Languages_Fixed |
|-------------|---------------|----------------------------------|
| Spain | 2 | Spanish and English |
| Sweden | 1 | English |
| Switzerland | 4 | French, German, Italian, English |
* Je ne voulais pas répéter les autres aussi en disant que c'est un bug.