【问题标题】:Expanding Multiple Time Series in a Table在表中展开多个时间序列
【发布时间】:2018-07-01 11:01:57
【问题描述】:

我有一个包含多个时间序列的表,每个时间序列都与一个给定的 ID 相关联。

类似的东西

+--------------------------------------+
| ID | start      | end        | value |
+--------------------------------------+
| a  | 01/01/2018 | 03/01/2018 | 5     |
| a  | 03/01/2018 | 04/01/2018 | 6     |
| a  | 04/01/2018 | 06/01/2018 | 7     |
| b  | 01/01/2018 | 04/01/2018 | 3     |
| b  | 04/01/2018 | 06/01/2018 | 4     |
+--------------------------------------+

我们看到时间序列是由不规则间隔定义的。我想“扩展”每个时间序列,以便该系列中每天有一行。

这样

+--------------------------------------+
| ID | start      | end        | value |
+--------------------------------------+
| a  | 01/01/2018 | 02/01/2018 | 5     |
| a  | 02/01/2018 | 03/01/2018 | 5     |
| a  | 03/01/2018 | 04/01/2018 | 6     |
| a  | 04/01/2018 | 05/01/2018 | 7     |
| a  | 05/01/2018 | 06/01/2018 | 7     |
| b  | 01/01/2018 | 02/01/2018 | 3     |
| b  | 02/01/2018 | 03/01/2018 | 3     |
| b  | 03/01/2018 | 04/01/2018 | 3     |
| b  | 04/01/2018 | 05/01/2018 | 4     |
| b  | 05/01/2018 | 06/01/2018 | 4     |
+--------------------------------------+

是否可以在 SQL 中从前者获取后者表,如果可以,请指出正确的方向

注意:每个时间序列都是连续的,没有重叠的区间。

【问题讨论】:

  • 我不明白。到目前为止,您尝试过什么?
  • 如果没有某种循环和某种临时表,我看不出如何填充它。
  • 您需要在查询语句中执行它还是可以是过程/函数?
  • 您想“创建”数据来填补空白,对吧?
  • 哦,我找到了一种使用 ROW_NUMBER 和 DATEDIFF 的方法。我在这里进行实验,我会尽快发布答案。

标签: sql sql-server time-series


【解决方案1】:

你可以试试这个。

DECLARE @T TABLE (ID VARCHAR(2),  start DATE, [end] DATE, value INT )
INSERT INTO @T VALUES
( 'a', '01/01/2018', '01/03/2018', 5 ),
( 'a', '01/03/2018', '01/04/2018', 6 ),
( 'a', '01/04/2018', '01/06/2018', 7 ),
( 'b', '01/01/2018', '01/04/2018', 3 ),
( 'b', '01/04/2018', '01/06/2018', 4 )


;WITH CTE AS (
    SELECT * FROM @T 
    UNION ALL
    SELECT T.ID, DATEADD(DAY,1, CTE.start) Start, T.[end], T.value 
    FROM @T T 
        INNER JOIN CTE ON T.ID = CTE.ID AND T.value = CTE.value 
            AND DATEADD(DAY,1, CTE.start)  < T.[end]
)
SELECT ID , start, DATEADD(DAY,1, start) [end], value FROM CTE
ORDER BY ID, start

结果:

ID   start      end        value
---- ---------- ---------- -----------
a    2018-01-01 2018-01-02 5
a    2018-01-02 2018-01-03 5
a    2018-01-03 2018-01-04 6
a    2018-01-04 2018-01-05 7
a    2018-01-05 2018-01-06 7
b    2018-01-01 2018-01-02 3
b    2018-01-02 2018-01-03 3
b    2018-01-03 2018-01-04 3
b    2018-01-04 2018-01-05 4
b    2018-01-05 2018-01-06 4

【讨论】:

  • 感谢您的宝贵时间。这在概念上/语法上对我来说似乎是最好的解决方案,但是对于我的需求来说它太慢了(输入表可以有 O(1e5) 行)。
  • 感谢您的好评。你是对的,对于你的问题,递归 cte 方法的工作速度比静态数值乘数表慢。
【解决方案2】:

这种方法将原始表与一个从 0 开始的整数表连接起来,只选择那些小于日期差的整数。所选的每一行都提供了扩展日期范围的记录之一。我假设原始数据中的日期间隔不超过 9999 天,但如果是,您可以通过为 tenthousands 等添加一行来扩展整数表。

SELECT  T.ID, 
        DATEADD(D, V.N, T.Start) [start], 
        DATEADD(D, V.N+1, T.Start) [end], 
        T.Value
    FROM YourTable T
    JOIN (
                SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n N
                    FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
                         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
                         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
                         (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)

        ) V
    ON V.N < DATEDIFF(D, T.Start, T.[End])

编辑:我应该承认@slartidan 在这个答案https://stackoverflow.com/a/33146869/1992793 中用于生成整数列表的优雅方式@

【讨论】:

  • 我采用了这种方法,因为它的速度足以满足我的需求。感谢您的宝贵时间。
【解决方案3】:

交叉应用通常是更快的选择。我假设您可以假设限制覆盖某个范围的月数,并假设您的输入日期始终落在该月的第一天。

with mnths as (
    select *
    from (values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11)) as t(offset)
)
select *
from data
    cross apply (
        select dateadd(month, offset, start_dt) as expanded_start_dt
        from mnths
        where dateadd(month, offset, start_dt) < end_dt
    ) as m

【讨论】:

  • 虽然有限间隔长度的假设是可以的,但从月初开始的范围却不是。感谢您的宝贵时间。
  • 很容易调整。这里最大的不同是使用cross apply
猜你喜欢
  • 2020-03-06
  • 2021-04-26
  • 2018-01-05
  • 1970-01-01
  • 2021-11-20
  • 2018-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多