【问题标题】:SQL interpolating missing values for a specific date range - with some conditionsSQL 为特定日期范围内插缺失值 - 在某些条件下
【发布时间】:2020-06-24 18:29:09
【问题描述】:

网站上有一些类似的问题,但我相信我的问题值得新帖,因为需要纳入特定条件。

我有一个每月间隔的表格,结构如下:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  1 |     10 | 12/17/2017   | 1/17/2018    |
|  1 |     10 | 1/18/2018    | 2/18/2018    |
|  1 |     10 | 2/19/2018    | 3/19/2018    |
|  1 |     10 | 3/20/2018    | 4/20/2018    |
|  1 |     10 | 4/21/2018    | 5/21/2018    |
+----+--------+--------------+--------------+

我发现有时在我知道应该存在的年末/年初左右缺少一个月的数据,如下所示:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  2 |     10 | 10/14/2018   | 11/14/2018   |
|  2 |     10 | 11/15/2018   | 12/15/2018   |
|  2 |     10 | 1/17/2019    | 2/17/2019    |
|  2 |     10 | 2/18/2019    | 3/18/2019    |
|  2 |     10 | 3/19/2019    | 4/19/2019    |
+----+--------+--------------+--------------+

我需要的是一个声明:

  1. 确定缺少此年终期间的位置(但未发现缺少 不是在年初/年末的月份)。
  2. 使用现有间隔的长度创建此间隔 该ID(也许使用ID的平均间隔长度来做到这一点?)。我可以从上一个和下一个间隔之间的“间隙”创建间隔,除非如果我在 ID 记录的开头或结尾缺少一个间隔(即如果记录从 1/16 开始/2015,我需要 12/15/2014-1/15/2015 的金额
  3. 使用每日平均值为此间隔插入一个“数量” 'amount' 来自最接近的现有区间。

上述示例的最终结果应如下所示:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  2 |     10 | 10/14/2018   | 11/14/2018   |
|  2 |     10 | 11/15/2018   | 12/15/2018   |
|  2 |     10 | 12/16/2018   | 1/16/2018    |
|  2 |     10 | 1/17/2019    | 2/17/2019    |
|  2 |     10 | 2/18/2019    | 3/18/2019    |
+----+--------+--------------+--------------+

'nice to have' 是一个标志,表示该值已被插值。

有没有办法在 SQL 中有效地做到这一点?我在 SAS 中编写了一个解决方案,但需要将其移至 SQL,并且我的 SAS 解决方案效率非常低(优化不是目标,所以任何符合我需要的语句都很棒)。

编辑:我在这里用我的示例表制作了一个 SQLFiddle:

http://sqlfiddle.com/#!18/8b16d

【问题讨论】:

  • 请标记一个 DBMS(Oracle、MySQL 等)。任何答案都需要针对您特定的 SQL 方言。
  • 参见:Why should I provide an MCRE for what seems to me to be a very simple SQL query? - 但考虑处理应用程序代码中的数据显示问题
  • 谢谢!我可能措辞不正确,因为我对 SQL 不是很熟悉(因此我要求解决应该是一个非常简单的过程的解决方案)。我不需要仅仅从数据库中查询,我想写入表,以便将这些信息存储在数据库中。我想要一个标志,以便我可以轻松返回并确定哪些值是插值的,哪些是“原始的”。
  • 您的小提琴是 MySQL,但问题标记为 SQL server。哪一个?小提琴中样本数据的预期输出是什么?
  • @AlJones1816 你的小提琴比你的问题有更多的数据。我只是想弄清楚您希望查询为小提琴中的数据生成哪些间隔。

标签: sql sql-server date time-series interpolation


【解决方案1】:

您可以使用一系列 CTE 来构建缺失期间的数据。在此查询中,第一个 CTE (EOYS) 生成与表相关的所有年终日期 (YYYY-12-31);第二个 (INTERVALS) 每个 ID 的平均间隔长度和第三个 (MISSING) 尝试为任何缺失 ( t1.ID IS NULL) 年终间隔。然后在INSERT ... SELECT 查询中使用此 CTE 的输出将缺失的间隔记录添加到表中,根据需要通过将间隔长度添加/减去相邻间隔的结束/开始日期来生成缺失日期。

虽然我们首先添加了interp 列来指示是否插入了一行:

ALTER TABLE Table1 ADD interp TINYINT NOT NULL DEFAULT 0;

这会将所有现有行的 interp 设置为 0。然后我们可以执行INSERT,将所有这些行的interp 设置为1

WITH EOYS AS (
  SELECT DISTINCT DATEFROMPARTS(DATEPART(YEAR, interval_beg), 12, 31) AS eoy
  FROM Table1
),
INTERVALS AS (
  SELECT ID, AVG(DATEDIFF(DAY, interval_beg, interval_end)) AS interval_len
  FROM Table1
  GROUP BY ID
),
MISSING AS (
  SELECT e.eoy, 
         ids.ID, 
         i.interval_len, 
         COALESCE(t2.amount, t3.amount) AS amount, 
         DATEADD(DAY,  1, t2.interval_end) AS interval_beg, 
         DATEADD(DAY, -1, t3.interval_beg) AS interval_end
  FROM EOYS e
  CROSS JOIN (SELECT DISTINCT ID FROM Table1) ids
  JOIN INTERVALS i ON i.ID = ids.ID
  LEFT JOIN Table1 t1 ON ids.ID = t1.ID
                     AND e.eoy BETWEEN t1.interval_beg AND t1.interval_end
  LEFT JOIN Table1 t2 ON ids.ID = t2.ID
                     AND DATEADD(MONTH, -1, e.eoy) BETWEEN t2.interval_beg AND t2.interval_end
  LEFT JOIN Table1 t3 ON ids.ID = t3.ID
                     AND DATEADD(MONTH,  1, e.eoy) BETWEEN t3.interval_beg AND t3.interval_end
  WHERE t1.ID IS NULL
)
INSERT INTO Table1 (ID, amount, interval_beg, interval_end, interp)
SELECT ID,
       amount,
       COALESCE(interval_beg, DATEADD(DAY, -interval_len, interval_end)) AS interval_beg,
       COALESCE(interval_end, DATEADD(DAY,  interval_len, interval_beg)) AS interval_end,
       1 AS interp
FROM MISSING

这会将以下行添加到表中:

ID  amount  interval_beg    interval_end    interp
2   10      2017-12-05      2018-01-04      1
2   10      2018-12-16      2019-01-16      1
2   10      2019-12-28      2020-01-27      1

Demo on SQLFiddle

【讨论】:

  • 这太棒了,非常感谢!您的描述非常清楚,我能够遵循并从中学习。感谢您的耐心等待,因为我还是个新手并且正在学习适当的礼仪。
  • @AlJones1816 不用担心 - 我很高兴能提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多