24

当我在手动构建的数据集上使用 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
4

2 回答 2

7

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

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

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

order_by_expression

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

...

ASC | DESC

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

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

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


更新

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

于 2013-09-12T19:36:23.013 回答
-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 用于空值,因为它应该按升序对字段进行排序,因此非空值将优先)

干杯 - 洛杉矶。

于 2015-04-10T04:23:44.107 回答