【问题标题】:Unexpected results when using FIRST_VALUE() in SQL Server 2012在 SQL Server 2012 中使用 FIRST_VALUE() 时出现意外结果
【发布时间】:2013-09-16 03:13:14
【问题描述】:

当我在手动构建的数据集上使用 FIRST_VALUE 时,我得到一个结果,当我在左连接产生的数据集上使用它时,我得到不同的结果 - 即使数据集看起来我包含完全相同的数据值。我用下面的一个简单数据集重现了这个问题。

如果我误解了什么,谁能告诉我?

此 SQL 产生预期结果,即 FIRST_VALUE 为 NULL,LAST_VALUE 为 30。

SELECT
  agroup,
  aval,
  FIRST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fv,
  LAST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) lv
FROM
(
  SELECT 1 agroup, 10 aval
  UNION ALL SELECT 1, NULL
  UNION ALL SELECT 1, 30
) T

此 SQL 使用 LEFT JOIN 生成与上述相同的数据集,但 FIRST_VALUE 似乎忽略了 NULL。

SELECT 
  agroup,
  aval,
  FIRST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fv,
  LAST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) lv
FROM
(
  SELECT 
    T1.agroup,
    T1.akey,
    T2.aval 
  FROM 
  (
    SELECT 1 agroup, 1 akey
    UNION ALL SELECT 1, 2
    UNION ALL SELECT 1, 3
  ) T1
  LEFT JOIN
  (
    SELECT 1 akey, 10 aval
    UNION ALL SELECT 3,30
  ) T2 ON T1.akey = T2.akey
) T

我还可以证明,当使用表变量和 CTE 时,左连接行为是不同的。当使用 CTE 生成数据时,FIRST_VALUE 会忽略 NULL。使用完全相同的 SQL,但将结果放在表变量或临时表中会导致 NULL 被考虑在内。

使用 CTE,SQL Server 结果在 FIRST_VALUE 确定中不包括 NULL:

WITH T AS
(
  SELECT 
    T1.agroup,
    T1.akey,
    T2.aval 
  FROM 
  (
    SELECT 1 agroup, 1 akey
    UNION ALL SELECT 1, 2
    UNION ALL SELECT 1, 3
  ) T1
  LEFT JOIN
  (
    SELECT 1 akey, 10 aval
    UNION ALL SELECT 3,30
  ) T2 ON T1.akey = T2.akey
)

SELECT 
  agroup,
  aval,
  FIRST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fv,
  LAST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) lv
FROM
 T

但是使用表变量,它确实:

DECLARE @T TABLE (agroup INT,akey INT,aval INT)

INSERT INTO
  @T
SELECT 
  T1.agroup,
  T1.akey,
  T2.aval 
FROM 
(
  SELECT 1 agroup, 1 akey
  UNION ALL SELECT 1, 2
  UNION ALL SELECT 1, 3
) T1
LEFT JOIN
(
  SELECT 1 akey, 10 aval
  UNION ALL SELECT 3,30
) T2 ON T1.akey = T2.akey


SELECT 
agroup,
aval,
FIRST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fv,
LAST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY aval ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) lv
FROM
@T

【问题讨论】:

  • SQLfiddle 同意你的看法。
  • 看起来像一个错误,我看到一些关于 FIRST_VALUE()LAST_VALUE() 的 Connect 项目,但没有任何与 ORDER BY 和空值相关的内容。您使用的 SQL-Server 的确切版本是什么?您完成所有更新了吗?
  • 是的,Connect 站点。查看类似的Connect item, regarding LAST_VALUE() and CTEs
  • 我看到你打开了一个关于这个的新连接项。 link for reference
  • 我同意 ypercube 和 MartinSmith 的观点,这很可能是最罕见的询问,是产品中新发现的错误。出色的工作,+1。

标签: sql-server tsql sql-server-2012 aggregate-functions window-functions


【解决方案1】:

对这篇文章的回答有点晚,但还是要分享一个。

您可以使用 order by 标志来“降级”空值。

所以在你的情况下......你可以使用

... FIRST_VALUE(aval) OVER (PARTITION BY agroup ORDER BY (iif(aval is null, 1,0)), aval ROWS BETWEEN UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING) fv ...

(请注意,我将值 1 用于空值,因为它应该按升序对字段进行排序,因此非空值将优先)

干杯 - 洛杉矶。

【讨论】:

  • “降级”空值一开始就是错误的行为。正确的行为是公开空值,但由于存在错误,SQL Server 在某些情况下不会这样做。您的查询将隐藏空值,并且如果我没记错的话,可能会以一种糟糕的方式阻止正确使用索引。
  • 是的,这不仅取决于数据在 from 中的生成方式,还取决于您在 FIRST_VALUE 函数中选择的字段。例如,给定一个按某个类别划分的数据集,如果您按该类别中的字段 C 排序,使得字段 A 中的空值应该排在第一位并且应该被选中,SQL Server 将改为选择字段 A 中的第一个非空值,但前提是 from 子句是连接或 CTE。同样,如果您选择具有相似属性的字段 B,它将再次无法从正确的行中生成 null,而是会选择第一个非 null 值。
【解决方案2】:

提供的示例非常清楚地表明,FIRST_VALUE() 分析函数的实现存在不一致。

在一种情况下,取决于FROM 子句中的基础表是否是基表(或临时表或表变量,甚至是动态创建的派生表)以及由LEFT JOIN 在第二种情况下创建了两个动态表,结果不同。似乎NULL 值在第二种情况下被忽略或被视为高值。

它们不应该不同,因为 SQL 查询的结果不应该取决于 FROM 子句如何获取它提供给 SELECT 子句的表的值,并且还因为 @987654322 的文档@ 子句清楚地说明了应该如何对待 NULL 值:

order_by_expression

指定要排序的列或表达式。 order_by_expression 只能引用 FROM 子句提供的列。不能指定整数来表示列名或别名。

...

ASC | DESC

指定指定列中的值应按升序或降序排序。 ASC 是默认的排序顺序。 Null 值被视为可能的最低值

因此,根据 SQL-Server 文档,正确的结果是不忽略 NULL 值的结果。任何其他结果都不应该发生,因为它确实发生了,这是一个错误

我建议您在最新版本中进行测试(不仅在 RTM 中),因为它可能已在某些服务包或更新中被识别和更正,并且如果它仍然存在(或者如果您没有较新的版本)可用)将此作为错误提交到 Connect 站点中。


更新

为了将来参考,该错误由 OP 提交。链接是:Connect item 和(我们的)@Aaron Bertrand 在那里评论说它也出现在大多数当前的 SQL 2014 版本中。

【讨论】:

  • Connect 项因无法修复而关闭。
  • @GSerg,当微软拒绝修复这个错误时,我感到非常失望,因为这意味着 SQL Server 在正确使用时会产生已知的错误输出。希望没有人将此功能用于关键任务结果。即使修复太难,系统至少可以发出警告,说明查询的输出已知不正确。
  • 这可能是最近修复的,比如 2016 年 11 月 13 日。我们在 SQL Azure 中的一个生产查询突然开始从 FIRST_VALUE 函数中产生 NULL,而以前没有。经过仔细检查,产生空值实际上是正确的行为,因此我不得不更新 partition by 子句以使用 case 语句排除空值。就像 OP 建议的那样,我无法在小型手动数据集上产生问题,而且它似乎根据数据的生成方式以及您从 FIRST_VALUE 中选择的值来表现出来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-07
  • 1970-01-01
  • 2017-10-04
  • 1970-01-01
  • 2021-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多