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

use*_*830 4 t-sql sql-server

我有一个包含大部分为空值的时间序列数据的表,我想用最后一个已知值填充所有空值。

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

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

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
Run Code Online (Sandbox Code Playgroud)

生产表

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
...
Run Code Online (Sandbox Code Playgroud)

我有 > 1 亿行,所以这需要很长时间。

use*_*983 5

这看起来像是一个“经典”的缺口和岛屿问题。假设您没有使用 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;
Run Code Online (Sandbox Code Playgroud)

数据库<>小提琴

  • `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` 是默认窗口@gouravkr,我只是更喜欢定义它;以防万一情况发生变化。 (2认同)

EzL*_*zLo 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
Run Code Online (Sandbox Code Playgroud)

这是我针对类似问题实现的解决方案,您可以在这里找到详尽的解释。这种方法很好的原因是它不需要显式排序,只要您有一个索引即可date

它的作用基本上是使用一个窗口,MAX其中包含组成日期列的 3 个字节的串联(这就是为什么我提到您的列必须是DATE,否则DATETIME将需要 8 个字节,您可以编辑查询来使用它)与组成价格列的字节(也假设为 5 个字节)。这就是这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为空价格值):

date        price   ff_price    WindowedMax
2016-07-10  NULL    NULL        NULL
2016-07-11  0.79    0.79        0x9B3B0B050200014F
2016-07-12  NULL    0.79        0x9B3B0B050200014F
2016-07-13  NULL    0.79        0x9B3B0B050200014F
2016-07-14  0.69    0.69        0x9E3B0B0502000145
2016-07-15  NULL    0.69        0x9E3B0B0502000145
2016-07-21  0.88    0.88        0xA53B0B0502000158
2016-07-22  NULL    0.88        0xA53B0B0502000158
Run Code Online (Sandbox Code Playgroud)