使用T-SQL向前填充时间序列数据中的空值的有效方法

时间:2019-02-26 16:03:51

标签: sql-server tsql

我有一个表,其中包含时间序列数据,大多数为null,我想用最后一个已知值填充所有null。

我有一些解决方案,但是它们比在Pandas中执行等效的DataFrame.fillna(method='ffill')操作要慢得多。

我正在使用的代码/数据的简化版本:

select d.[date], d.[price],
       (select top 1 p.price from price_table p
        where p.price is not null and p.[date] <= p.[date]
        order by p.[date] desc) as ff_price
from price_table d

产生表格

date       price ff_price
---------- ----- --------
2016-07-11 0.79  0.79
2016-07-12 NULL  0.79
2016-07-13 NULL  0.79
2016-07-14 0.69  0.69
2016-07-15 NULL  0.69
...
2016-09-21 0.88  0.88
...

我有1亿多行,所以要花很长时间。

3 个答案:

答案 0 :(得分:2)

这看起来像一个“经典”的鸿沟和孤岛问题。 假设您没有使用2008年或更早的版本(它们(几乎)全部不在支持范围内),则应该为您提供您所追求的结果:

WITH CTE AS(
    SELECT [date],
           price,
           COUNT(CASE WHEN price IS NOT NULL THEN 1 END) OVER (ORDER BY [date]
                                                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
    FROM price_table p)
SELECT [date],
       price,
       MIN(price) OVER (PARTITION BY grp) AS ff_price
FROM CTE;

db<>fiddle

答案 1 :(得分:1)

您也可以使用APPLY

SELECT t.*, t1.price AS ff_price
FROM price_table t OUTER APPLY
     (SELECT TOP (1) t1.*
      FROM price_table t1
      WHERE t1.[date] <= t.[date] AND t1.price IS NOT NULL
      ORDER BY t1.[date] DESC
     ) t1;

答案 2 :(得分:1)

假设您的列为DATE,价格为DECIMAL(5,2),请测试此方法:

SELECT
    P.[date],
    P.[price],
    ff_price = CONVERT(
        DECIMAL(5,2),       -- Original price datatype
        SUBSTRING(
            MAX(
                CAST(P.[date] AS BINARY(3)) +   -- 3: datalength of P.[date] column
                CAST(P.[price] AS BINARY(5))    -- 5: datalength of P.[price] column
            ) OVER (ORDER BY P.[date] ROWS UNBOUNDED PRECEDING),

            4,  -- Position to start that's not the binary part of the date

            5))-- Characters that compose the binary of the original price datatype
FROM
    price_table  AS P

这是我为解决类似问题而实施的解决方案,您可以找到详尽的解释here。这种方法之所以好,是因为只要您有date的索引, 它就不需要显式排序

它的作用基本上是使用带窗口的MAX和由3个字节组成的日期列的串联(这就是为什么我提到您的列必须为DATE,否则为{{1} }将需要8个字节,您可以编辑查询以对此进行处理),其中包含构成价格列的字节(也假定为5个字节)。这是DATETIME部分。

当您计算此值和CAST(P.[date] AS BINARY(3)) + CAST(P.[price] AS BINARY(5))时,引擎基本上会使用最大有效字节是您的日期的值进行最大滚动。当日期更改时,最大结果将始终更新,但考虑到将任何值与ORDER BY P.[date] ROWS UNBOUNDED PRECEDING连接起来也将产生NULL(作为二进制),那么NULL将始终忽略此值并保留前一个非空的MAX(由MAX保留)。

这是窗口P.[date] ROWS UNBOUNDED PRECEDING的二进制结果(我添加了MAX的前一条记录,因此对于空价格值,您看到的结果是NULL

NULL