偶然发现了一个有趣的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
答案 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个。