SQL 为特定日期范围内插缺失值 - 在某些条件下
SQL interpolating missing values for a specific date range - with some conditions
网站上有一些类似的问题,但我相信我的问题需要一个新的 post,因为需要纳入特定条件。
我有一个 table 以月为间隔,结构如下:
+----+--------+--------------+--------------+
| 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 |
+----+--------+--------------+--------------+
我发现有时在我知道应该存在的 end/beginning 年份前后会丢失一个月的数据,如下所示:
+----+--------+--------------+--------------+
| 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 |
+----+--------+--------------+--------------+
我需要的是一个声明:
- 识别这个年末期间缺失的地方(但不寻找缺失
不是 的月份 beginning/end。
- 使用现有间隔的长度创建此间隔
该ID(也许使用ID的平均间隔长度来做到这一点?)。我可以从上一个和下一个间隔之间的 "gap" 创建间隔,除非我在 ID 记录的开头或结尾缺少一个间隔(即如果记录从 1 开始) /16/2015,我需要12/15/2014-1/15/2015的金额
- 使用每日平均值为此间隔插入一个 'amount'
'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 |
+----+--------+--------------+--------------+
A 'nice to have' 将是一个标志,表示该值是插值的。
有没有办法在 SQL 中有效地做到这一点?我已经用 SAS 编写了一个解决方案,但需要将其移动到 SQL,而且我的 SAS 解决方案效率非常低(优化不是目标,所以任何满足我需要的语句都很棒)。
编辑:我在这里SQL摆弄我的例子table:
您可以使用一系列 CTE 来构建缺失周期的数据。在此查询中,第一个 CTE (EOYS
) 生成与 table 相关的所有年终日期 (YYYY-12-31
);第二个 (INTERVALS
) 每个 ID
的平均间隔长度和第三个 (MISSING
) 尝试找到开始(从 t2
)和结束(从 t3
) 任何缺失(由 t1.ID IS NULL
表示)年末间隔的相邻间隔的日期。然后在 INSERT ... SELECT
查询中使用此 CTE 的输出,以将缺失的间隔记录添加到 table,通过 adding/subtracting 到 end/start 日期的间隔长度生成缺失日期必要时的相邻间隔。
虽然我们首先添加 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
这会将以下行添加到 table:
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
网站上有一些类似的问题,但我相信我的问题需要一个新的 post,因为需要纳入特定条件。
我有一个 table 以月为间隔,结构如下:
+----+--------+--------------+--------------+
| 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 |
+----+--------+--------------+--------------+
我发现有时在我知道应该存在的 end/beginning 年份前后会丢失一个月的数据,如下所示:
+----+--------+--------------+--------------+
| 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 |
+----+--------+--------------+--------------+
我需要的是一个声明:
- 识别这个年末期间缺失的地方(但不寻找缺失 不是 的月份 beginning/end。
- 使用现有间隔的长度创建此间隔 该ID(也许使用ID的平均间隔长度来做到这一点?)。我可以从上一个和下一个间隔之间的 "gap" 创建间隔,除非我在 ID 记录的开头或结尾缺少一个间隔(即如果记录从 1 开始) /16/2015,我需要12/15/2014-1/15/2015的金额
- 使用每日平均值为此间隔插入一个 'amount' '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 |
+----+--------+--------------+--------------+
A 'nice to have' 将是一个标志,表示该值是插值的。
有没有办法在 SQL 中有效地做到这一点?我已经用 SAS 编写了一个解决方案,但需要将其移动到 SQL,而且我的 SAS 解决方案效率非常低(优化不是目标,所以任何满足我需要的语句都很棒)。
编辑:我在这里SQL摆弄我的例子table:
您可以使用一系列 CTE 来构建缺失周期的数据。在此查询中,第一个 CTE (EOYS
) 生成与 table 相关的所有年终日期 (YYYY-12-31
);第二个 (INTERVALS
) 每个 ID
的平均间隔长度和第三个 (MISSING
) 尝试找到开始(从 t2
)和结束(从 t3
) 任何缺失(由 t1.ID IS NULL
表示)年末间隔的相邻间隔的日期。然后在 INSERT ... SELECT
查询中使用此 CTE 的输出,以将缺失的间隔记录添加到 table,通过 adding/subtracting 到 end/start 日期的间隔长度生成缺失日期必要时的相邻间隔。
虽然我们首先添加 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
这会将以下行添加到 table:
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