2

我在使用 mysql 语句计算给定时间段内一天的约会时遇到了麻烦。我有一个日历表,包括开始和结束列(类型 = DateTime)。以下语句应计算 11 月的所有约会,包括总体约会:

SELECT 
    COUNT('APPOINTMENTS') AS Count, 
    DATE(c.StartingDate) AS Datum 
FROM t_calendar c
    WHERE 
        c.GUID = 'blalblabla' AND
        ((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
        ((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
    GROUP BY DATE(c.StartingDate)
    HAVING Count > 1

但是如何包含在 StartingDate 之前开始并在 StartingDate 结束的约会?

例如

  • 起始日期 = 2012-11-14 17:00:00,结束日期 = 2012-11-15 08:00:00
  • 起始日期 = 2012-11-15 09:00:00,结束日期 = 2012-11-15 10:00:00
  • 起始日期 = 2012-11-15 11:00:00,结束日期 = 2012-11-15 12:00:00

我的报表在 11 月 15 日返回计数 2。但这是错误的,因为第一次约会不见了。如何包括这些约会?我缺少什么,UNION SELECT,JOIN,子选择?


一个可能的解决方案?

SELECT 
    c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count, 
    DATE(c1.StartingDate) AS Datum,
    COUNT(DISTINCT c2.ANYFIELD)
FROM 
    t_calendar c1
LEFT JOIN
    t_calendar c2
        ON
            c2.ResourceGUID = c1.ResourceGUID AND
            (DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND 
            (DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE 
    ((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
    ((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))    
GROUP BY 
    c1.ResourceGUID,
    DATE(c1.StartingDate)
4

1 回答 1

4

第一:合并范围检查

首先,您的两个范围where条件可以用一个替换。而且似乎您只计算完全重叠目标日期范围或完全包含在其中的约会。不包括部分重叠的。因此,您关于在范围开始日期结束的约会的问题。

为了使where子句易于理解,我将使用以下方法对其进行简化:

  • 定义目标范围的两个变量:
    • rangeStart(在你的情况下,2012 年 11 月 1 日)
    • rangeEnd(我宁愿假设到 2012 年 12 月 1 日 00:00:00.00000)
  • 不会像您那样datetime仅转换为日期(使用date函数),但您可以轻松地做到这一点。

考虑到这些,您的where条款可以大大简化并涵盖给定范围的所有约会:

...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...

这将搜索所有在目标范围内的约会,并将涵盖所有这些约会案例:

                           start          end
target range               |==============|

partial front       |---------|
partial back                            |---------|
total overlap          |---------------------|
total containment               |-----|

部分正面/背面也可能几乎不会触及您的目标范围(您一直在追求的范围)。

二:解决问题

为什么你错过了第一张唱片?仅仅因为您的having条款仅收集从某一天开始有超过 1 个约会的团体:11 月 15 日有两个,但 14 日只有一个,因此被排除在外,因为Count = 1不是> 1

要回答您的第二个问题,我缺少的是:您没有遗漏任何东西,实际上您的陈述中有太多内容需要简化。

试试这个语句,而不是应该返回你所追求的:

select count(c.GUID) as Count,
       date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
      (c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
      (c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)

我使用str_to_date函数使字符串到日期的转换更加安全。

我不太确定您为什么要having在声明中包含它,因为它并不是真正需要的。除非您的实际陈述更复杂,并且您只包含最相关的部分。在这种情况下,您可能必须将其更改为:

having Count > 0

在任何给定日期范围内获取每天的约会计数

可能还有其他方法,但最常见的方法是使用数字或 ?calendar* 表,使您能够将范围分解为单个- 天。他们必须将您的约会加入此数字表并提供结果。

创建了一个可以解决问题的 SQLFiddle。这就是它的作用......

假设您有数字表Num,其中数字从 0 到x。和约会表Cal与您的记录。以下脚本创建了这两个表并填充了一些数据。数字最多只有 100 个,足以存储 3 个月的数据。

-- appointments
create table Cal (
  Id int not null auto_increment primary key,
  StartDate datetime not null,
  EndDate datetime not null
);

-- create appointments
insert Cal (StartDate, EndDate)
values
  ('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
  ('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
  ('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
  ('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
  ('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
  ('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
  ('2012-12-01 05:00:00', '2012-12-10 12:00:00');

-- numbers table
create table Nums (
  Id int not null primary key
);

-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
from (select 0 as a union all
      select 1 union all
      select 2 union all
      select 3 union all
      select 4 union all
      select 5 union all
      select 6 union all
      select 7 union all
      select 8 union all
      select 9) as a,
     (select 0 as a union all
      select 1 union all
      select 2 union all
      select 3 union all
      select 4 union all
      select 5 union all
      select 6 union all
      select 7 union all
      select 8 union all
      select 9) as b

现在你要做的是

  1. Num通过从表格中选择数字并将它们转换为日期来选择您所做的日期范围。
  2. 然后将您的约会加入这些日期,以便将那些在特定日期的约会加入到该特定日期
  3. 然后每天将所有这些约会分组并获得结果

这是执行此操作的代码:

-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;

-- start and end target date range
set @s := str_to_date('2012-11-01', '%Y-%m-%d');
set @e := str_to_date('2012-12-01', '%Y-%m-%d');

-- get appointment count per day within target range of days
select adddate(@s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
  left join Cal c
  on ((date(c.StartDate) <= adddate(@s, n.Id)) and (date(c.EndDate) >= adddate(@s, n.Id)))
where adddate(@s, n.Id) < @e
group by Day;

这是这个相当简单的选择语句的结果:

|        DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 |            2 |
| 2012-11-02 |            0 |
| 2012-11-03 |            0 |
| 2012-11-04 |            0 |
| 2012-11-05 |            0 |
| 2012-11-06 |            0 |
| 2012-11-07 |            0 |
| 2012-11-08 |            0 |
| 2012-11-09 |            0 |
| 2012-11-10 |            0 |
| 2012-11-11 |            0 |
| 2012-11-12 |            0 |
| 2012-11-13 |            0 |
| 2012-11-14 |            0 |
| 2012-11-15 |            1 |
| 2012-11-16 |            1 |
| 2012-11-17 |            0 |
| 2012-11-18 |            0 |
| 2012-11-19 |            0 |
| 2012-11-20 |            1 |
| 2012-11-21 |            1 |
| 2012-11-22 |            1 |
| 2012-11-23 |            1 |
| 2012-11-24 |            1 |
| 2012-11-25 |            1 |
| 2012-11-26 |            1 |
| 2012-11-27 |            1 |
| 2012-11-28 |            1 |
| 2012-11-29 |            1 |
| 2012-11-30 |            2 |
于 2012-11-14T10:00:22.760 回答