Sql 字符串_AGG的行为与预期不符

Sql 字符串_AGG的行为与预期不符,sql,sql-server,sql-server-2017,string-aggregation,Sql,Sql Server,Sql Server 2017,String Aggregation,我有以下疑问: WITH cteCountryLanguageMapping AS ( SELECT * FROM ( VALUES ('Spain', 'English'), ('Spain', 'Spanish'), ('Sweden', 'English'), ('Switzerland', 'English'), ('Switzerland',

我有以下疑问:

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个值由
分隔):

我错过了什么


下面是另一个例子:

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中的一个bug吗?

是的,这是一个bug(tm),存在于SQL Server 2017的所有版本中(截至撰写时)。它已在Azure SQL Server和2019 RC1中修复。具体地说,优化器中执行公共子表达式消除的部分(确保我们计算表达式的次数不超过必要的次数)不正确地认为只要
x
匹配,所有
字符串\u AGG(x,)
形式的表达式都是相同的,无论
是什么,并将它们与查询中的第一个计算表达式相统一

一种解决方法是通过对
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

这似乎是一个bug,它总是返回第一个字符串\u AGG,无论您如何编写案例。这是一个优化程序bug的优点。更简单、更引人注目的复制:
CASE COUNT([Language])当1234567然后是STRING_AGG([Language],'and'),否则STRING_AGG([Language],',')结束为[Languages]
(使用
1234567
CASE)和
CASE COUNT([Language]),当1234567然后是STRING_AGG([Language],'and')结束为[Languages
(省略
ELSE
——现在匹配失败,表达式变为
NULL
)。无论“正确”是什么结果应该是,当然不是。不确定这是否合适,但是……哈哈!因为这不是你缺乏知识,而不是许多人会遇到的真实情况,我强烈建议将问题标题更改为比一般的“未按预期工作”更准确,以使所有人的利益最大化。在生成的执行计划中,第二个
字符串_AGG
完全丢失,相反,表达式反弹到第一个,就好像
案例
说了
字符串_AGG([语言],“和”)
两次。任何后续的
案例都会被吸收。看起来子表达式消除过程中发生了一些非常奇怪的事情。此错误似乎被特别调整为
字符串\u AGG
。如果
ELSE
更改为
'blargh'+STRING\u AGG(…)
,您将得到
'blarghFrench and German…
,因此它不正确地将第二个
STRING_AGG
与第一个统一起来。最简单的解决方法是将
ELSE
表达式更改为
STRING_AGG([语言]+'','))
--这击败了CSE,表明CSE忽略了
STRING\u AGG
的第二个参数,这是一个错误。我允许自己发布此解决方法,指出它仍然损坏,即使在CU17中也是如此。@RossPresser:重新测试了它,果然,CU17实际上并没有修复此问题。修改了答案。
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