LAST_VALUE、CURRENT ROW 和 NULL 的意外结果
Posted
技术标签:
【中文标题】LAST_VALUE、CURRENT ROW 和 NULL 的意外结果【英文标题】:Unexpected results with LAST_VALUE, CURRENT ROW, and NULLs 【发布时间】:2014-10-06 12:51:16 【问题描述】:我有以下数据:
CREATE TABLE #Transfers (
AddedOn DATETIME2 NOT NULL,
EmpID INT NOT NULL,
NewDeptID INT NULL
)
INSERT INTO #Transfers
VALUES
('2013-12-17 17:18:54.3499987', 19, 36),
('2013-12-18 13:02:34.1168087', 19, NULL),
('2014-01-28 11:41:55.8755928', 22, 100),
('2014-02-05 10:36:36.3645703', 22, NULL),
('2014-02-16 00:00:00.0000000', 22, 37),
('2014-02-17 00:00:00.0000000', 22, NULL)
对于每一行,我都在尝试获取最新的非空 NewDeptID
(直到该行):
SELECT *,
LAST_VALUE(NewDeptID) OVER (
PARTITION BY EmpID
ORDER BY IIF(NewDeptID IS NULL,0,1), AddedOn
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS CurrentDeptID
FROM #Transfers
ORDER BY EmpID, AddedOn
如果我理解正确,应该排除空值,因为它们是窗口中的第一个值 -- IIF(NewDeptID IS NULL,0,1)
。
我希望得到以下结果:
AddedOn EmpID NewDeptID CurrentDeptID
2013-12-17 17:18:54.3499987 19 36 36
2013-12-18 13:02:34.1168087 19 NULL 36
2014-01-28 11:41:55.8755928 22 100 100
2014-02-05 10:36:36.3645703 22 NULL 100
2014-02-16 00:00:00.0000000 22 37 37
2014-02-17 00:00:00.0000000 22 NULL 37
相反,LAST_VALUE 中的 ORDER BY 子句被忽略,当当前行包含 NULL 时返回 NULL:
AddedOn EmpID NewDeptID CurrentDeptID
2013-12-17 17:18:54.3499987 19 36 36
2013-12-18 13:02:34.1168087 19 NULL NULL --
2014-01-28 11:41:55.8755928 22 100 100
2014-02-05 10:36:36.3645703 22 NULL NULL --
2014-02-16 00:00:00.0000000 22 37 37
2014-02-17 00:00:00.0000000 22 NULL NULL --
我在 SQL Server 2012 和 2014 中得到了相同的结果。
这是 SQL Server 中的错误,还是我在窗口函数语法中遗漏了什么?
注意:如果我扩展窗口以包含整个分区,则忽略 NULL:
SELECT *,
LAST_VALUE(NewDeptID) OVER (
PARTITION BY EmpID
ORDER BY IIF(NewDeptID IS NULL,0,1), AddedOn
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS CurrentDeptID
FROM #Transfers
ORDER BY EmpID, AddedOn
结果:
AddedOn EmpID NewDeptID CurrentDeptID
2013-12-17 17:18:54.3499987 19 36 36
2013-12-18 13:02:34.1168087 19 NULL 36
2014-01-28 11:41:55.8755928 22 100 37
2014-02-05 10:36:36.3645703 22 NULL 37
2014-02-16 00:00:00.0000000 22 37 37
2014-02-17 00:00:00.0000000 22 NULL 37
【问题讨论】:
【参考方案1】:不,你还没有很好地理解窗口函数是如何工作的。 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
在ORDER BY IIF...
之后应用,因此首先对行进行排序(首先是具有NULL
的行,然后是其余所有行),然后应用ROWS ...
限制。所以,这永远不会解决你的问题。
详细地说,OVER
子句根据PARTITION
和ORDER BY
创建了这些“窗口”。
因此,例如对于带有AddedOn = '2014-02-17 00:00:00.0000000'
的行,ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
是上面的行及其本身(标有<------
的两行):
AddedOn EmpID IIF() NewDeptID
-- partition EmpID = 19
2013-12-18 13:02:34.1168087 19 0 NULL
2013-12-17 17:18:54.3499987 19 1 36
-- partition EmpID = 22
2014-02-05 10:36:36.3645703 22 0 NULL <---
2014-02-17 00:00:00.0000000 22 0 NULL <--- this is the LAST_VALUE
2014-01-28 11:41:55.8755928 22 1 100
2014-02-16 00:00:00.0000000 22 1 37
所以,CurrentDeptID
列获取这些值:
AddedOn EmpID IIF() NewDeptID CurrentDeptID
-- partition EmpID = 19
2013-12-18 13:02:34.1168087 19 0 NULL NULL
2013-12-17 17:18:54.3499987 19 1 36 36
-- partition EmpID = 22
2014-02-05 10:36:36.3645703 22 0 NULL NULL
2014-02-17 00:00:00.0000000 22 0 NULL NULL
2014-01-28 11:41:55.8755928 22 1 100 100
2014-02-16 00:00:00.0000000 22 1 37 37
然后根据外部ORDER BY
重新排序以获得最终结果
要解决此问题,您可以使用相关子查询:
SELECT *,
( SELECT TOP (1) NewDeptID
FROM #Transfers AS ti
WHERE ti.EmpID = t.EmpID
AND ti.NewDeptID IS NOT NULL
AND ti.AddedOn <= t.AddedOn
ORDER BY AddedOn DESC
) AS CurrentDeptID
FROM #Transfers AS t
ORDER BY EmpID, AddedOn ;
在SQL-Fiddle测试
如果 LAST_VALUE()
函数可以忽略空值,那么您正在尝试做的事情实际上是有意义的。这可以通过 IGNORE NULLS
完成,但不幸的是,此功能尚未在 SQL-Server 中实现。您可以在 Oracle (fiddle-2) 中查看它的工作原理:
SELECT AddedOn, EmpID, NewDeptID,
LAST_VALUE(NewDeptID)
IGNORE NULLS -- check this
OVER (
PARTITION BY EmpID
ORDER BY AddedOn
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS CurrentDeptID
FROM Transfers
ORDER BY EmpID, AddedOn ;
在 SQL-Server 中使用窗口函数的另一种方法是首先计算当前行之前的非空 NewDeptID
。您可以通过 fiddle-3 进行测试:
WITH cte AS
( SELECT AddedOn, EmpID, NewDeptID,
COUNT(NewDeptID) OVER (
PARTITION BY EmpID
ORDER BY AddedOn
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cnt
FROM Transfers
)
SELECT AddedOn, EmpID, NewDeptID,
MIN(NewDeptID) OVER (PARTITION BY EmpID, cnt) AS CurrentDeptID
FROM cte
ORDER BY EmpID, AddedOn ;
【讨论】:
我不明白。如果首先应用ORDER BY
,则具有空值的记录首先在分区内。在计算分区内直到当前行的最后一个值时,结果不应为空,除非分区中所有先前记录的值也为空。
但是你说的是,“分区内有空值的记录在前”。如果一行为空,则它之前的所有行也为空。当我写ORDER BY
时,我指的是ORDER BY IIF...
,而不是外部订单。
您的IIF()
不允许这样做(所有非空行都放在所有空行之后)。我编辑了我的答案,详细说明了窗口的工作原理。
所以LAST_VALUE
和ROWS BETWEEN ... AND CURRENT ROW
总是会返回当前行的值? FIRST_VALUE
和 ROWS BETWEEN CURRENT ROW AND ...
一样吗?
是的,谢谢。已更正。 (是的,正是上一个问题)以上是关于LAST_VALUE、CURRENT ROW 和 NULL 的意外结果的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server ->> FIRST_VALUE和LAST_VALUE函数
自定义elementUI表格中的highlight-current-row