分组日期之间的间隔

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 &lt;= :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 &lt;= 3 更改为适合您需要的任何内容。

以上是关于分组日期之间的间隔的主要内容,如果未能解决你的问题,请参考以下文章

按日期间隔分组 Oracle

Eloquent - 按“从日期”到“至今”分组,间隔为 1 天

按日期间隔选择多行分组(导致重复)[重复]

SQL查询中按日期、天间隔分组

3.1.4MySQL__数据库分组,拼接查询,日期函数,日期加减,间隔,数值四舍五入,排序,分组,having筛选,分组TopN,流程控制函数,

按日期间隔大于 X 的 DATETIME 获取数据、计数和分组