计算日期范围内的天数,同时排除重叠天数

时间:2016-03-25 09:38:47

标签: mysql date count overlapping

我正在寻找几个日期范围内的天数。我使用datediff函数来计算天数,但现在我想排除重叠天数。因此,从最古老的日期开始,直到我想要在日期范围内有天数,如果它在重叠范围内,则每天只计算一次。

我的表格如下:

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.因为最后一行与第二行完全重叠。

有没有人有解决方案?

2 个答案:

答案 0 :(得分: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具有某种程序感觉&#34;当谈到select陈述时。实际上,您可以在其中使用变量(@id@ed),这些变量会随着时间的推移而改变其状态(在此上下文中,order by子句非常重要。)

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

当前case更改时,必须使用第一个@id子句重置变量@edpid

子查询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

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

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

答案 1 :(得分:0)

此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

要求给定人员的句点是唯一的,因此没有两个句点与 end_date 具有相同的 start_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 |