分组日期之间的间隔
Posted
技术标签:
【中文标题】分组日期之间的间隔【英文标题】:Grouping Gaps between Dates 【发布时间】:2019-09-16 19:26:34 【问题描述】:我正在尝试找到一个函数,让我可以创建将原始日期转换为剧集的功能,我可以在其中设置构成剧集的参数。对于此示例,日期之间的 3 天间隔将是一个新的“情节”,但最终希望将其缩放为任意 x =
我已经尝试过间隙和孤岛问题以及领先和滞后 1 天的时间段,但无法获得代码来为每个 3 天的窗口间隔提供一行。
drop table RUNNING_LOG;
create table running_log (
run_date date not null,
time_in_seconds int not null,
distance_in_miles int not null
);
truncate table running_log;
begin
insert into running_log values (date'2018-01-01', 420, 1);
insert into running_log values (date'2018-01-02', 2400, 5);
insert into running_log values (date'2018-01-03', 2430, 5);
insert into running_log values (date'2018-02-06', 2350, 5);
insert into running_log values (date'2018-02-07', 410, 1);
insert into running_log values (date'2018-07-10', 400, 1); ---4 month gap
insert into running_log values (date'2018-08-13', 2300, 5);
insert into running_log values (date'2018-12-31', 425, 1);
insert into running_log values (date'2019-01-01', 422, 1);
insert into running_log values (date'2019-01-06', 2350, 5);
insert into running_log values (date'2019-02-07', 410, 1);
insert into running_log values (date'2019-06-10', 400, 1);
insert into running_log values (date'2019-07-13', 2300, 5);
insert into running_log values (date'2019-08-14', 425, 1);
insert into running_log values (date'2019-12-15', 422, 1);
insert into running_log values (date'2020-01-01', 425, 1);
insert into running_log values (date'2020-03-31', 422, 1);
insert into running_log values (date'2020-04-15', 422, 1);
insert into running_log values (date'2020-06-01', 425, 1);
insert into running_log values (date'2020-07-06', 425, 1);
insert into running_log values (date'2021-03-31', 422, 1);
commit;
end;
select * from running_log
理想情况下,我想构建一个表,其中包含第 3 天或更长时间的 begin_date 和少于 3 天的聚合行的聚合数据。下面的代码只做 1 天的间隔,并且不为每个间隔提供 begin_date end_date。
with grps as (
select run_date
, row_number() over (order by run_date) rn ,
run_date - row_number() over (order by run_date) grp_date
from running_log
)
select min(run_date) first_run
, max(run_date) last_run,
count(*) runs,
row_number() over (order by min(run_date)) grp
from grps
group by grp_date
order by min(run_date)
再一次,一行将是 1. 3 天的间隔或 3 天之间的聚合。
【问题讨论】:
谢谢 Tinkinc。您使用的是什么数据库版本? 【参考方案1】:我会推荐 MATCH_RECOGNIZE
进行此类查询。
它存在的理由是检测多行的模式。
对于您提供的数据集并使用 3 天作为组间的间隔,示例如下:
SELECT EPISODE_NUMBER, FIRST_RUN, LAST_RUN, RUNS_IN_EPISODE
FROM RUNNING_LOG
MATCH_RECOGNIZE (
ORDER BY RUN_DATE
MEASURES
MATCH_NUMBER() AS EPISODE_NUMBER,
MIN(RUN_DATE) AS FIRST_RUN,
MAX(RUN_DATE) AS LAST_RUN,
COUNT(*) AS RUNS_IN_EPISODE
PATTERN (EPISODE_START IN_EPISODE*)
DEFINE IN_EPISODE AS RUN_DATE <= PREV(RUN_DATE + 3));
结果:
EPISODE_NUMBER FIRST_RUN LAST_RUN RUNS_IN_EPISODE
_________________ ____________ ____________ __________________
1 01-JAN-18 03-JAN-18 3
2 06-FEB-18 07-FEB-18 2
3 10-JUL-18 10-JUL-18 1
4 13-AUG-18 13-AUG-18 1
5 31-DEC-18 01-JAN-19 2
6 06-JAN-19 06-JAN-19 1
7 07-FEB-19 07-FEB-19 1
8 10-JUN-19 10-JUN-19 1
9 13-JUL-19 13-JUL-19 1
10 14-AUG-19 14-AUG-19 1
11 15-DEC-19 15-DEC-19 1
12 01-JAN-20 01-JAN-20 1
13 31-MAR-20 31-MAR-20 1
14 15-APR-20 15-APR-20 1
15 01-JUN-20 01-JUN-20 1
16 06-JUL-20 06-JUL-20 1
17 31-MAR-21 31-MAR-21 1
17 rows selected.
MATCH_RECOGNIZE
有这样的解剖结构:
ORDER BY
只定义了处理行的顺序MEASURES
是针对每个分区的分析PATTERN
是定义剧集的位置。这里是EPISODE_START
的任何运行,然后是零次或多次运行IN_EPISODE
DEFINE
是模式使用的标准。这是运行间隔多少天才有资格成为IN_EPISODE
上面的 [+ 3] 可以上下调整,以适应或多或少的包容性群体。
使用 [+ 100] 代替,制作更少的剧集,每集的运行次数更多
结果:
EPISODE_NUMBER FIRST_RUN LAST_RUN RUNS_IN_EPISODE
_________________ ____________ ____________ __________________
1 01-JAN-18 07-FEB-18 5
2 10-JUL-18 13-AUG-18 2
3 31-DEC-18 07-FEB-19 4
4 10-JUN-19 14-AUG-19 3
5 15-DEC-19 06-JUL-20 6
6 31-MAR-21 31-MAR-21 1
6 rows selected.
【讨论】:
遗憾的是我在 11g 上。叹息 @Tinkinc 哎哟。这太糟糕了。它对此无济于事,但也许值得在某个时候与您的 DBA 谈谈。迁移到 18/19c 的权利可能会受到影响,但迁移到 12.1 非常干净。【参考方案2】:这确实是某种差距和孤岛问题。
您需要多个级别的子查询来处理这个问题。首先计算ROW_NUMBER()
并使用LAG()
得到之前的run_date
值,然后根据连续记录之间的差距进行条件求和。最后,行号和条件和之间的差异为您提供了组。
考虑:
SELECT
MIN(run_date) first_run,
MAX(run_date) last_run,
COUNT(*) runs
FROM (
SELECT
x.*,
SUM(CASE WHEN run_date - lg <= 3 THEN 1 ELSE 0 END) OVER(ORDER BY run_date) rn2
FROM (
SELECT
t.*,
ROW_NUMBER() OVER(ORDER BY run_date) rn1,
LAG(run_date) OVER(ORDER BY run_date) lg,
FROM running_log t
) x
) y
GROUP BY rn1 - rn2
ORDER BY 1
要适应允许间隙的长度,您可以简单地修改条件和中的表达式:run_date - lg <= :max_gap_length
此demo on DB Fiddle 与您的示例数据返回:
第一次运行 |上次运行 |运行 :-------- | :-------- | ---: 2018 年 1 月 1 日 | 2018 年 1 月 3 日 | 3 18 年 2 月 6 日 | 2018 年 2 月 7 日 | 2 18 年 7 月 10 日 | 18 年 7 月 10 日 | 1 18 年 8 月 13 日 | 18 年 8 月 13 日 | 1 18 年 12 月 31 日 | 19 年 1 月 1 日 | 2 2019 年 1 月 6 日 | 2019 年 1 月 6 日 | 1 2019 年 2 月 7 日 | 2019 年 2 月 7 日 | 1 19 年 6 月 10 日 | 19 年 6 月 10 日 | 1 19 年 7 月 13 日 | 19 年 7 月 13 日 | 1 19 年 8 月 14 日 | 19 年 8 月 14 日 | 1 19 年 12 月 15 日 | 19 年 12 月 15 日 | 1 20 年 1 月 1 日 | 20 年 1 月 1 日 | 1 20 年 3 月 31 日 | 20 年 3 月 31 日 | 1 20 年 4 月 15 日 | 20 年 4 月 15 日 | 1 01-JUN-20 | 01-JUN-20 | 1 20 年 7 月 6 日 | 20 年 7 月 6 日 | 1 21 年 3 月 31 日 | 21 年 3 月 31 日 | 1注意:那是一个有趣的问题!
【讨论】:
我需要将所有内容汇总为最小值和最大值,尤其是在相差不到 3 天或 x 天时 @Tinkinc:这正是查询打算做的......当我在 db fiddle 中针对您的示例数据运行查询时,您是否在查询结果中看到有问题? 是的,它不会根据间隔按连续顺序创建剧集。假设记录了 75 次跑步,我们想看看有人在日期 x 和日期 y 之间停止跑步超过 40 天的时间。这将是一个休息和一个新的情节,直到同样的事情以大于 40 的间隔再次发生。小于 40 的所有天都被捆绑到一个情节中。 @Tinkinc:再一次,这就是查询的作用。事实上,一旦它检测到连续 2 次运行之间的间隔大于 3 天,它就会进入一个新的情节(即一个新的记录)。您可以根据需要调整间隙的大小,方法是将表达式run_date - lg <= 3
更改为适合您需要的任何内容。以上是关于分组日期之间的间隔的主要内容,如果未能解决你的问题,请参考以下文章
Eloquent - 按“从日期”到“至今”分组,间隔为 1 天
3.1.4MySQL__数据库分组,拼接查询,日期函数,日期加减,间隔,数值四舍五入,排序,分组,having筛选,分组TopN,流程控制函数,