【问题标题】:MySQL count day of week between two dates over a tableMySQL计算表上两个日期之间的星期几
【发布时间】:2018-03-18 17:45:09
【问题描述】:

我有一个包含以下列的预订表:

id, start_date, end_date

我想知道在我的数据集中哪几天的预订量最多。

我可以在开始日期使用 dayofweek() 并按此分组并使用计数 (*)。但我也想包括预订开始和结束之间的天数。

一个示例输出将是

dayofweek     count(*)
1             1
2             1
3             1
4             2
5             3
6             3
7             1

对于以下集合

id       start_date          end_date
1        2017-10-01          2017-10-07
2        2017-10-04          2017-10-07
3        2017-10-06          2017-10-08

【问题讨论】:

  • 使用日历表(包含您需要的所有日期)并使用 BETWEEN 条件将其加入您的表中。
  • 不幸的是 10 月是从星期日开始的。

标签: mysql count dayofweek


【解决方案1】:

我假设您想知道从开始到结束期间每个日期有多少房间被占用。这里的“技巧”是开始/结束之间的长时间将重复一天或一周和/或一周的结束日期可能小于一周的开始日期。所以,我有:

  1. 生成了一个包含 100,000 个日期的列表(每行 1 个)
  2. 加入表格开始/结束之间的日期
  3. 将每个连接的行转换为要计算的星期几号
  4. left join 到 1 到 7 的列表,并计算第 3 步的行数

注意:如果 end_date 是“退房日期”,则可能需要从每条记录中扣除 1 天以进行补偿(以下未做)。

此方法可在SQL Fiddle 处查看

MySQL 5.6 架构设置

CREATE TABLE Table1
    (`id` int, `start_date` datetime, `end_date` datetime)
;

INSERT INTO Table1
    (`id`, `start_date`, `end_date`)
VALUES
    (1, '2017-09-21 00:00:00', '2017-10-07 00:00:00'), ## added this row
    (1, '2017-10-01 00:00:00', '2017-10-07 00:00:00'),
    (2, '2017-10-04 00:00:00', '2017-10-07 00:00:00'),
    (3, '2017-10-06 00:00:00', '2017-10-08 00:00:00')
;

查询

set @commence := str_to_date('2000-01-01','%Y-%m-%d')

select
    w.dy
  , count(t.wdy)
from (
      select 1 dy union all select 2 dy union all select 3 dy union all
      select 4 dy union all select 5 dy union all select 6 dy union all select 7 dy
      ) w
left join (
      select DAYOFWEEK(cal.dy) wdy
      from (
              select adddate( @commence ,t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) dy 
              from  (     select 0 i 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) t0 
              cross join (select 0 i 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) t1 
              cross join (select 0 i 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) t2 
              cross join (select 0 i 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) t3 
              cross join (select 0 i 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) t4
          ) cal
      INNER JOIN Table1 t on cal.dy between t.start_date and t.end_date
      ) t on w.dy = t.wdy
group by
    w.dy

Results

| dy | count(t.wdy) |
|----|--------------|
|  1 |            4 |
|  2 |            3 |
|  3 |            3 |
|  4 |            4 |
|  5 |            5 |
|  6 |            6 |
|  7 |            6 |

另请参阅:How to get list of dates between two dates in mysql select query,其中接受的答案是从指定日期开始产生 100,000 个日期的交叉连接集的基础。但是我修改了它的语法(显式交叉连接语法),一个参数作为起点,并使用union all 来提高效率。

【讨论】:

  • 这行得通!但是我有一个包含 100 行的测试表,目前需要 20 秒,如果说我有 100,000 行,这个查询的运行情况如何?
  • 一个。我无法真正预测到这一点,并且 b。它很可能取决于您的索引和c。而不是动态生成的日期列表,您可以创建一个表并对其进行索引。始终参考解释计划以了解性能细节。如果询问有关性能的新问题,请包括表和相关索引的完整 DDL。加上解释计划输出(作为文本)
【解决方案2】:

您可以使用递归表来完成此操作:

WITH cte AS
(
    SELECT DATE_ADD(start_date INTERVAL 1 DAY) AS date, end_date, DAYOFWEEK(start_date) AS dw from bookings
    UNION ALL 
    SELECT DATE_ADD(start_date INTERVAL 1 DAY), end_date, DAYOFWEEK(date)
    FROM cte WHERE date <= end_date
)
SELECT COUNT(*), dw FROM cte GROUP BY dw

【讨论】:

  • 附加说明:递归表可能不可用,具体取决于所使用的版本(即 MariaDB
  • 使用 MySQL 5.7
猜你喜欢
  • 2015-10-13
  • 2013-03-30
  • 1970-01-01
  • 1970-01-01
  • 2016-09-04
  • 1970-01-01
  • 1970-01-01
  • 2011-11-25
  • 1970-01-01
相关资源
最近更新 更多