在SQL Server 2012中使用FIRST_VALUE()时出现意外结果

时间:2013-09-11 22:26:32

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

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

2 个答案:

答案 0 :(得分:7)

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

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

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

  

order_by_expression

     

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

     

...

     

ASC | DESC

     

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

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

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


更新

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

答案 1 :(得分:-1)

对这篇文章有点迟到的回答,不过要分享一下。

您可以按标志使用顺序来降级"降级"空值。

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

... FIRST_VALUE(aval)OVER(分组由agroup ORDER BY (iif(aval为null,1,0)),aval 在无限制前进和无限制下行之间的行程)fv ...

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

干杯 - 洛杉矶。