集团日期范围为几个月

时间:2014-02-04 12:38:39

标签: php mysql sql date group-by

偶然发现了一个有趣的SQL问题。

我有一个日期范围表,每个条目都有一个开始和结束日期。由此我需要获得每个日历月的天数。

例如,如果有一个事件开始于20-02-2014并结束于10-03-2014,我需要一个结果,列出02-2014,8天,03-2014,10。这也需要总结所有事件,我都是在每个月的总天数之后。

我可以考虑如何使用PHP执行此操作,但希望看到仅SQL解决方案。

编辑: 为了更好地了解这里的情况,这与我的表格http://sqlfiddle.com/#!2/4232b/1类似,我会在结果中找到类似

的结果。
+---------------------+
| year | month | days |
+---------------------+
| 2014 |     2 |    8 |
| 2014 |     3 |   18 |
| 2014 |     5 |   32 |
| 2014 |     6 |    1 |
| 2014 |     9 |    9 |
| 2014 |    10 |   10 |
| 2014 |    11 |   30 |
| 2014 |    12 |    1 |
+------+-------+------+

希望那里没有任何数学错误:P

2 个答案:

答案 0 :(得分:0)

好吧,快点玩。这可以应对长达100个月的差异,但是没有经过多少测试(特别是对于边缘情况,例如2个日期都在同一个月/年),我怀疑我可能有错误的日期差异参数在某个地方,所以有些人可能会消极。

此外,我已将天数差异计入不包括开始日期(正如您在2月20日至月底之间的8天示例中所示)

SELECT DATE_FORMAT(DATE_ADD('2013-01-01', INTERVAL MonthsDiff MONTH), '%Y %m'),
    IF (MONTH('2014-01-01') = MONTH('2013-01-01') AND YEAR('2014-03-01') = YEAR('2013-01-01'), DATEDIFF('2014-01-01', '2013-01-01'), 
    CASE
        WHEN MonthsDiff = 0
            THEN DATEDIFF(LAST_DAY('2013-01-01'), '2013-01-01')
        WHEN MonthsDiff = ((YEAR('2014-03-01') - YEAR('2013-01-01')) * 12) + (MONTH('2014-03-01') - MONTH('2013-01-01'))
            THEN DAYOFMONTH('2014-03-01')
        ELSE DAYOFMONTH(LAST_DAY(DATE_ADD('2013-01-01', INTERVAL MonthsDiff MONTH)))
    END ) AS DaysDifferencePerMonth
FROM
(
    SELECT units.aMonth + tens.aMonth * 10 as MonthsDiff
    FROM
    (SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units,
    (SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
    HAVING ((YEAR('2014-03-01') - YEAR('2013-01-01')) * 12) + (MONTH('2014-03-01') - MONTH('2013-01-01')) >= MonthsDiff
) Sub1

编辑 - 上面的解决方案给了我一个想法。

一对日期的纯SQL版本(最多999天): -

SELECT YEAR(DATE_ADD('2014-02-15', INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY )) AS aYear,
        MONTH(DATE_ADD('2014-02-15', INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY )) AS aMonth,
        COUNT(*)
FROM
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units,
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens,
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) hundreds
WHERE DATE_ADD('2014-02-15', INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY ) <= '2014-12-10'
GROUP BY aYear, aMonth

将其与您的事件表相对应: -

SELECT events.start_date, events.end_date, YEAR(DATE_ADD(events.start_date, INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY )) AS aYear,
        MONTH(DATE_ADD(events.start_date, INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY )) AS aMonth,
        COUNT(*)
FROM events,
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units,
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens,
(SELECT 0 AS aMonth UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) hundreds
WHERE DATE_ADD(events.start_date, INTERVAL units.aMonth + tens.aMonth * 10  + hundreds.aMonth * 100 DAY ) <= events.end_date
GROUP BY events.start_date, events.end_date, aYear, aMonth
ORDER BY events.start_date, events.end_date, aYear, aMonth;

请注意,这些都包括最后一天。将&lt; =更改为&lt;如果你不想算那一天。

答案 1 :(得分:0)

您在所需的输出中有一些错误的计算。后来证明了。

要轻松解决问题,请创建一个仅包含日期的表格,无论如何都是个好主意。我是这样做的:

DROP PROCEDURE IF EXISTS sp_date_range;
DELIMITER $$
CREATE PROCEDURE sp_date_range(IN startdate date, IN enddate date)
BEGIN
DROP TABLE IF EXISTS date_range;
CREATE TABLE date_range(a_date date primary key);

SET @date := startdate;

WHILE (@date <= enddate) DO
INSERT INTO date_range VALUES (@date);
SET @date := @date + INTERVAL 1 DAY;
END WHILE;

END $$
DELIMITER ;

并称之为:

CALL sp_date_range('2014-02-15', '2014-12-10');

只需使日期范围足够大。

然后像这样查询你的表:

select
year(a_date), month(a_date), count(*)
from
events e
inner join date_range d on d.a_date between e.start_date and e.end_date
group by 1, 2;

结果将是:

+--------------+---------------+----------+
| year(a_date) | month(a_date) | count(*) |
+--------------+---------------+----------+
|         2014 |             2 |        9 |
|         2014 |             3 |       19 |
|         2014 |             5 |       34 |
|         2014 |             6 |        1 |
|         2014 |             9 |       10 |
|         2014 |            10 |       11 |
|         2014 |            11 |       30 |
|         2014 |            12 |        1 |
+--------------+---------------+----------+

现在为了证明,你错误地计算了预期的结果:

mysql> select
    -> *
    -> from
    -> events e
    -> inner join date_range d on d.a_date between e.start_date and e.end_date
    -> where month(a_date) = 2
    -> ;

结果:

+----------+---------------------+---------------------+------------+
| event_id | start_date          | end_date            | a_date     |
+----------+---------------------+---------------------+------------+
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-20 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-21 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-22 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-23 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-24 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-25 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-26 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-27 |
|        1 | 2014-02-20 00:00:00 | 2014-03-10 00:00:00 | 2014-02-28 |
+----------+---------------------+---------------------+------------+

9行/天,你只计算了8个。