【问题标题】:Unexpected results with LAST_VALUE, CURRENT ROW, and NULLsLAST_VALUE、CURRENT ROW 和 NULL 的意外结果
【发布时间】:2026-01-25 12:30:01
【问题描述】:

我有以下数据:

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

【问题讨论】:

    标签: sql-server sql-server-2012 window-functions sql-server-2014


    【解决方案1】:

    不,你还没有很好地理解窗口函数是如何工作的。 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWORDER BY IIF... 之后应用,因此首先对行进行排序(首先是具有NULL 的行,然后是其余所有行),然后应用ROWS ... 限制。所以,这永远不会解决你的问题。

    详细地说,OVER 子句根据PARTITIONORDER 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_VALUEROWS BETWEEN ... AND CURRENT ROW 总是会返回当前行的值? FIRST_VALUEROWS BETWEEN CURRENT ROW AND ... 一样吗?
    • 是的,谢谢。已更正。 (是的,正是上一个问题)
    最近更新 更多