为什么我的 STRING_AGG 中的 ORDER BY 并不总是有效?

Reb*_*cca 21 sql-server t-sql string-aggregation

我有一个表,其中包含记录 ID、组 ID(将 1 个或多个记录链接到一个组中)以及每个记录的哈希值。

CREATE TABLE HashTable(
    RecordID VARCHAR(255),
    GroupIdentifier VARCHAR(255),
    Hash VARCHAR (255),
    GroupHashList VARCHAR(4000)
)
Run Code Online (Sandbox Code Playgroud)

(我知道这不是一个有效的表,但它只是用于本示例的临时表)。

我想为每个组生成一个哈希值,所以我认为最简单的方法是连接组中每个记录的哈希值。RecordID 是唯一的,但这些记录所涉及的内容不一定是唯一的,因此散列可能是重复的。这样做的目的是标记完全重复的组,即该组中的所有记录都是另一组中的所有记录的重复项的组。如果 GUI 要将组中的所有成员识别为重复组,则需要该组的所有成员具有相同的哈希值。

我使用 STRING_AGG 连接组中记录的各个散列,并按散列对它们进行排序,以确保我为重复的组获得相同的字符串。我实际上并不关心哈希的顺序是什么,只要每次都相同即可。当我将其作为 SELECT 查询运行时,它工作正常,并且我可以看到重复组的相同字符串。当我采用相同的 SELECT 查询并将其放入 UPDATE 查询中时,顺序似乎丢失了。

SELECT STRING_AGG([Hash],';') WITHIN GROUP (ORDER BY [Hash] ASC) 
FROM HashTable
GROUP BY [GroupIdentifier]
Run Code Online (Sandbox Code Playgroud)

这给出了结果(对于一对重复组的示例):

CREATE TABLE HashTable(
    RecordID VARCHAR(255),
    GroupIdentifier VARCHAR(255),
    Hash VARCHAR (255),
    GroupHashList VARCHAR(4000)
)
Run Code Online (Sandbox Code Playgroud)

当我将相同的代码放入 UPDATE 查询中时,它无法正确对它们进行排序:

UPDATE HashTable
SET GroupHashList = c.HashList
FROM HashTable
INNER JOIN (
    SELECT (STRING_AGG([Hash],';') WITHIN GROUP (ORDER BY [Hash] ASC)) AS [HashList],
        [GroupIdentifier]
    FROM HashTable
    GROUP BY [GroupIdentifier]) c
ON c.[GroupIdentifier] = HashTable.[GroupIdentifier]
Run Code Online (Sandbox Code Playgroud)

写入表中的相同两组的结果:

SELECT STRING_AGG([Hash],';') WITHIN GROUP (ORDER BY [Hash] ASC) 
FROM HashTable
GROUP BY [GroupIdentifier]
Run Code Online (Sandbox Code Playgroud)

我缺少什么?

我第一次得到的是

Hash1; Hash2; Hash3
Hash1; Hash2; Hash3
Run Code Online (Sandbox Code Playgroud)

但是当它在 UPDATE 查询中时,我得到

Hash1; Hash2; Hash3
Hash1; Hash3; Hash2
Run Code Online (Sandbox Code Playgroud)

更新查询是按记录 ID 排序的,尽管我不知道这是否是巧合。(https://dbfiddle.uk/CPG1-z2l

Cha*_*ace 25

这似乎是优化器中的一个错误。

优化器意识到连接是自连接,正在将其转换为窗口聚合。STRING_AGG尽管它不能作为窗口聚合使用,但它仍然可以做到这一点。该规则称为GenGbApplySimple,并允许将自连接转换为窗口聚合。到目前为止,这并没有什么特别的问题。

计划

粘贴计划

问题在于聚合超过了错误的值。它聚合的是外部价值而不是内部价值。

如果您为两个引用指定不同的别名,那么仔细检查查询计划就会发现该错误。

STRING_AGG([dbo].[HashTable].[Hash] as [HT1].[Hash],'')
WITHIN GROUP (ORDER BY [HT2].[Hash])
Run Code Online (Sandbox Code Playgroud)

另一个问题是与该规则一起使用的聚合(例如MINMAXAVG)没有WITHIN GROUP要满足的顺序,因此替换计划不考虑它。这似乎STRING_AGG并不适合GbApply规则,或者需要进行工作才能使其兼容(满足排序请求)。

如下所示,排序仅按相关列排序GroupIdentifier,而不是Hash按 中使用的列排序WITHIN GROUP

STRING_AGG([dbo].[HashTable].[Hash] as [HT1].[Hash],'')
WITHIN GROUP (ORDER BY [HT2].[Hash])
Run Code Online (Sandbox Code Playgroud)

如果您是sysadmin,则可以使用以下未记录的 来为查询关闭此规则OPTION

<OrderBy>
  <OrderByColumn Ascending="1">
    <ColumnReference
      Database="[...]"
      Schema="[dbo]"
      Table="[HashTable]"
      Alias="[HT1]"
      Column="GroupIdentifier">
    </ColumnReference>
  </OrderByColumn>
</OrderBy>
Run Code Online (Sandbox Code Playgroud)

作为解决方法,防止应用此优化的一个选项是使用分组OUTER APPLY

OPTION (QUERYRULEOFF GenGbApplySimple)
Run Code Online (Sandbox Code Playgroud)

这使您可以通过Stream Aggregate进行非常简单的自连接。

数据库<>小提琴


我强烈建议您将此作为错误提交给 Microsoft。

您也可以留下反馈,但这通常不会导致具体的回复。


顺便说一句,在编写多表语句时,您应该遵循Conor CunninghamUPDATE建议的别名规则:

非 ANSI FROM 子句(您在此处使用的)具有特定的绑定行为,这些行为可能符合您的预期,也可能不是您所期望的。我建议您首先将哈希表的 3 个引用别名化为不同的,然后确保您明确引用了您想要的引用。(我猜测)它可能与您想象的不同,并因此为您提供了不需要的输出。