【问题标题】:Count days within dateranges while excluding overlapping days计算日期范围内的天数,同时排除重叠天数
【发布时间】:2016-03-25 09:38:46
【问题描述】:

我正在寻找几个日期范围内的天数。我使用 datediff 函数对天数求和,但现在我想排除重叠天数。因此,从最早的日期直到 curdate,我想知道日期范围内的天数,如果它在重叠范围内,每天只计算一次。

我的桌子是这样的:

Person_id |      Start_date      | End_date              | Count
 83244       2014-09-01 00:00:00    2014-09-06 00:00:00    5
 83244       2014-09-08 00:00:00    2015-09-07 00:00:00    364
 83244       2015-01-15 00:00:00    2015-02-01 00:00:00    17

如果我把这个相加,我会得到 382,但我正在寻找的答案是 369。因为最后一行与第二行完全重叠。

有人有解决办法吗?

【问题讨论】:

  • 嗯,一开始似乎可行,但如果句点仅部分重叠,规则是什么?就像第三个时期是直到 2015 年 10 月 31 日? -- 啊,现在明白了,count 实际上给出了该范围内的天数,并不是一个独立的数据列。
  • 您使用的是哪个 RDBMS?
  • 是的,列计数是对日期范围内的天数的计数。试图让我的问题更容易解释。如果范围部分重叠,则应仅计算不重叠部分的天数
  • 使用 MySQL 数据库
  • 结束日期本身不计入期间的一部分,即它们是期间之后的第一天?

标签: mysql date count overlapping


【解决方案1】:

我已经用第二个Person_id 填充了您的示例,并稍微缩短了列名以使代码更短:

CREATE TABLE tbl(`pid` int, `sd` datetime, `ed` datetime);
INSERT INTO tbl (`pid`, `sd`, `ed`)
VALUES
    (83244, '2014-09-01', '2014-09-06'),
    (83244, '2014-09-08', '2015-09-07'),
    (83243, '2014-08-08', '2015-08-15'),
    (83243, '2014-08-11', '2015-09-03'),
    (83244, '2015-01-15', '2015-02-01');

因此,在处理上述数据时,您可以应用以下查询:

SELECT pid,sd,ed,CASE WHEN @id!=pid THEN @id:=pid+0*(@ed:=Date('1970-1-1')) END id, 
       CASE WHEN sd<@ed THEN CASE WHEN ed>@ed THEN datediff(ed,@ed) ELSE 0 END 
                        ELSE datediff(ed,sd) END days,
       @ed:=CASE WHEN ed>@ed THEN ed ELSE @ed END enddt
FROM tbl,( select @id:=0 ) const
ORDER BY pid,sd

与其他 RDBMS 不同,MySql 对 select 语句有一定的“程序感”。您实际上可以在其中使用变量(@id@ed),它们会随着时间的推移改变它们的状态(在这种情况下,末尾的 order by 子句非常重要)。

此查询背后的基本思想是:从某个pid 开始,并按开始日期递增的顺序列出间隔(sd)。始终记住变量 @ed 中的结束日期的最大值 (ed)。现在,对于每个新间隔,检查是否与前一个间隔重叠,即。 e.检查当前开始日期sd是否小于上一个(最大)结束日期(@ed)并相应地计算间隔days

每当当前pid 更改时,第一个case 子句是重置变量@id@ed 所必需的。

子查询const只是在开头设置了变量@id

查询产生以下结果:

  pid   sd                  ed                  id     days enddt
83243   2014-08-08 00:00:00 2015-08-15 00:00:00 83243   372 2015-08-15 00:00:00
83243   2014-08-11 00:00:00 2015-09-03 00:00:00          19 2015-09-03 00:00:00
83244   2014-09-01 00:00:00 2014-09-06 00:00:00 83244     5 2014-09-06 00:00:00
83244   2014-09-08 00:00:00 2015-09-07 00:00:00         364 2015-09-07 00:00:00
83244   2015-01-15 00:00:00 2015-02-01 00:00:00           0 2015-09-07 00:00:00 

在此处查看Demo

如果您只对总和感兴趣,您当然可以将整个查询包装在另一个 grouping 中,如下所示:

SELECT pid,sum(days) FROM (
 SELECT pid,sd,ed,CASE WHEN @id!=pid THEN @id:=pid+0*(@ed:=Date('1970-1-1')) END id, 
        CASE WHEN sd<@ed THEN CASE WHEN ed>@ed THEN datediff(ed,@ed) ELSE 0 END 
                         ELSE datediff(ed,sd) END days,
        @ed:=CASE WHEN ed>@ed THEN ed ELSE @ed END enddt
 FROM tbl,( select @id:=0 ) const
 ORDER BY pid,sd
) t GROUP BY pid ORDER BY pid

这会得到你

pid     sum(days)
83243   391
83244   369

【讨论】:

  • 非常感谢!将尝试此查询。
【解决方案2】:

此 SQL 将返回不计算重叠双倍的天数总和:

select    person_id, sum(days)
from      (
    select    t1.person_id,
              t1.start_date,
              t1.end_date,
              case when t1.end_date > coalesce(greatest(max(t2.end_date), t1.start_date), t1.start_date) 
                   then datediff(t1.end_date, coalesce(greatest(max(t2.end_date), t1.start_date), t1.start_date))
                   else 0
              end  days
    from      t  t1
    left join t  t2 on t1.person_id = t2.person_id
                   and (t2.start_date < t1.start_date
                    or t2.start_date = t1.start_date and t2.end_date < t1.end_date)
    group by  t1.person_id,
              t1.start_date,
              t1.end_date
    ) detail
group by person_id

对于给定的人,期间要求是唯一的,因此没有两个期间的 start_dateend_date 相同。

fiddle 为样本数据和人员返回 369。

另类

您可以创建一个序列表(这对许多用途都很有用),然后用它计算唯一的天数。

因此,作为一次性操作,您可以使用仅包含自然数(0、1、2 ...)的附加表来扩展您的数据库模型:

create table sequence (
  num int,
  primary key (num)
);

// Populate the above table with as many numbers as needed:
insert into sequence values(0);
insert into sequence select num+   1 from sequence; --    2 records
insert into sequence select num+   2 from sequence; --    4 records
insert into sequence select num+   4 from sequence; --    8 records
insert into sequence select num+   8 from sequence; --   16 records
insert into sequence select num+  16 from sequence; --   32 records
insert into sequence select num+  32 from sequence; --   64 records
insert into sequence select num+  64 from sequence; --  128 records
insert into sequence select num+ 128 from sequence; --  256 records
insert into sequence select num+ 256 from sequence; --  512 records
insert into sequence select num+ 512 from sequence; -- 1024 records
insert into sequence select num+1024 from sequence; -- 2048 records
insert into sequence select num+2048 from sequence; -- 4096 records

你可以继续插入这样的记录,但是对于当前的任务来说这已经绰绰有余了。

现在到实际的解决方案:

select     person_id, count(distinct num), count(num) 
from       sequence
cross join (select min(start_date) min_date,
                   max(end_date)   max_date
            from t) stats
inner join t
        on date_add(min_date, interval (num*24+12) hour)
           between start_date and end_date
where      num < datediff(max_date, min_date)
group by   person_id

此查询使用唯一数字来获取从最早开始日期开始的天数,并在它们处于一个周期内时包括这些日期。然后它计算满足该条件的唯一日期。

where 子句是可选的,但会加快查询速度。

这是fiddle。它产生这个结果:

| Person_id | count(distinct num) | count(num) |
|-----------|---------------------|------------|
|     83244 |                 369 |        386 |

【讨论】:

  • 将您的第一个解决方案应用于我的扩展样本数据(见上文)时,它只返回一个person_id 的单个结果。 “替代”解决方案完美运行,并且具有几乎不变的适用于任何 RDBMS 的优势(DATEDIFFdate_add 可能需要注意)!
  • 感谢您指出这一点,@cars10,我忘记在末尾添加group by person_id。现已修复。
  • 非常感谢您的回答。试试看!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-05
  • 1970-01-01
  • 2016-09-08
  • 2019-04-12
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
相关资源
最近更新 更多