2015-12-22 34 views
1

所以,我試圖建立在在日曆中的定期/重複事件 - >查找日期範圍內的事件

Calendar Recurring/Repeating Events - Best Storage Method

提出的架構和查詢上面的主要限制是查詢只返回事件一個特定的日期。我需要一個查詢任意日期範圍(單獨的查詢有效範圍內的每個日期是不可接受)

我的架構:

CREATE TABLE `events` ( 
    `event_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `title` varchar(128) NOT NULL, 
    `description` varchar(1000) DEFAULT NULL, 
    `date_added` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (`event_id`), 
) ENGINE=InnoDB AUTO_INCREMENT=4268 DEFAULT CHARSET=utf8 

CREATE TABLE `events_dates` (
    `events_date_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `event_id` bigint(20) unsigned NOT NULL, 
    `date_start` date NOT NULL, 
    `date_end` date NOT NULL, 
    `time_start` time DEFAULT NULL, 
    `time_end` time DEFAULT NULL, 
    `repeat_interval` int(11) DEFAULT NULL COMMENT 'simple repetition: 1: repeat every x days, 2: every-other day, 7: every-7 days', 
    `repeat_year` smallint(4) DEFAULT NULL COMMENT 'NULL ("*") or year', 
    `repeat_month` tinyint(2) DEFAULT NULL COMMENT 'NULL ("*") or month (1-12)', 
    `repeat_day` tinyint(2) DEFAULT NULL COMMENT 'NULL ("*") or day of month (1-31)', 
    `repeat_nth_day` tinyint(1) DEFAULT NULL COMMENT 'use in combination with repeat_weekday', 
    `repeat_weekday` tinyint(1) DEFAULT NULL COMMENT 'NULL ("*") or 1=Sunday, 7=Saturday', 
    PRIMARY KEY (`events_date_id`), 
    KEY `event_id` (`event_id`), 
    CONSTRAINT `events_dates_ibfk_1` FOREIGN KEY (`event_id`) REFERENCES `events` (`event_id`) ON DELETE CASCADE ON UPDATE CASCADE 
) ENGINE=InnoDB AUTO_INCREMENT=4268 DEFAULT CHARSET=utf8 

該日期將重複4日每月週四在2016年

`date_start` : "2016-01-01" 
`date_end` : "2016-12-31" 
`time_start` : NULL, 
`time_end` : NULL, 
`repeat_interval` : NULL 
`repeat_year` : NULL 
`repeat_month` : NULL 
`repeat_day` : NULL 
`repeat_nth_day` : 4 
`repeat_weekday` : 5 

和查詢來獲得發生在一個日期的事件:

CREATE DEFINER=`root`@`localhost` PROCEDURE `events_get_date`(_date DATE) 
BEGIN 

    DECLARE _year INT DEFAULT YEAR(_date); 
    DECLARE _month INT DEFAULT MONTH(_date); 
    DECLARE _day INT DEFAULT DAYOFMONTH(_date); 
    DECLARE _nth_day INT DEFAULT 1 + floor((DAYOFMONTH(_date) - 1)/7); 
    DECLARE _weekday INT DEFAULT DAYOFWEEK(_date); 

    SELECT e.*, 
     ed.`date_start`, 
     ed.`date_end`, 
     ed.`time_start`, 
     ed.`time_end` 
    FROM `events` e 
    JOIN `events_dates` ed ON ed.`event_id` = e.`event_id` 
    WHERE 
     (
      (`date_start` <= _date) AND 
      (`date_end` >= _date) AND 
      (DATEDIFF(_date, `date_start`) % `repeat_interval` = 0) 
     ) OR 
     (
      (`date_start` <= _date) AND 
      (`date_end` >= _date) AND 
      (`repeat_year` IS NULL OR `repeat_year` = _year) AND 
      (`repeat_month` IS NULL OR `repeat_month` = _month) AND 
      (`repeat_day` IS NULL OR `repeat_day` = _day) AND 
      (`repeat_nth_day` IS NULL OR `repeat_nth_day` = _nth_day) AND 
      (`repeat_weekday` IS NULL OR `repeat_weekday` = _weekday) 
     ) 
    GROUP BY e.`event_id`; 

END 

我卡住拿出一個過程返回的日期範圍

特別地,如果一個事件利用repeat_nth_day和repeat_weekday ...中發生的事件
例如:repeat_nth_day = 4和repeat_weekday = 5(月的第四星期四) 。查詢單個日期時很平常,但我不知道如何在日期範圍內執行此操作。

這裏是我到目前爲止有:

CREATE DEFINER=`root`@`localhost` PROCEDURE `_events_filter_get_range`(_date_start DATE, _date_end DATE) 
BEGIN 

    DECLARE _dom_start INT DEFAULT DAYOFMONTH(_date_start); 
    DECLARE _dom_end INT DEFAULT DAYOFMONTH(_date_end); 
    DECLARE _month_start INT DEFAULT MONTH(_date_start); 
    DECLARE _month_end INT DEFAULT MONTH(_date_end); 
    DECLARE _year_start INT DEFAULT YEAR(_date_start); 
    DECLARE _year_end INT DEFAULT YEAR(_date_end); 
    DECLARE _day_diff INT DEFAULT DATEDIFF(_date_end, _date_start); 
    DECLARE _month_diff INT DEFAULT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM _date_end), EXTRACT(YEAR_MONTH FROM _date_start)); 
    DECLARE _year_diff INT DEFAULT _year_end - _year_start; 
    #DECLARE _nth_day INT DEFAULT 1 + floor((DAYOFMONTH(_date) - 1)/7); 
    #DECLARE _weekday INT DEFAULT DAYOFWEEK(_date); 

    SELECT 
     e.*, 
     ed.`date_start`, 
     ed.`date_end`, 
     ed.`time_start`, 
     ed.`time_end` 
    FROM `events` e 
    JOIN `events_dates` ed ON ed.`event_id` = e.`event_id` 
    WHERE 
     (
      (`date_start` <= _date_end) AND 
      (`date_end` >= _date_start) AND 
      (ABS(DATEDIFF(_date_end, `date_start`)) % `repeat_interval` <= _day_diff) 
     ) OR 
     (
      (`date_start` <= _date_end) AND 
      (`date_end` >= _date_start) AND 
      (`repeat_year` IS NULL OR 
       `repeat_year` BETWEEN _year_start AND _year_end) AND 
      (`repeat_month` IS NULL OR 
       (_month_diff >= 11) OR 
       (_year_diff = 0 AND `repeat_month` BETWEEN _month_start AND _month_end) OR 
       # Dec 2015 - Jan 2015 
       (_year_diff = 1 AND (`repeat_month` <= _month_end OR `repeat_month` >= _month_start)) 
      ) AND 
      (`repeat_day` IS NULL OR 
       (_month_diff > 1) OR 
       # Jan 25 - Feb 26 
       (_month_diff = 1 AND _dom_start < _dom_end) OR 
       # Jan 25 - Feb 5 
       (_month_diff = 1 AND _dom_start > _dom_end AND (`repeat_day` <= _dom_end OR `repeat_day` >= _dom_start)) OR 
       # Jan 25 - Jan 26 
       (_month_diff = 0 AND _dom_start < _dom_end AND `repeat_day` BETWEEN _dom_start AND _dom_end) 
      ) 
      /* 
       Here's where I'm stuck.. 
       How do I check if a date range contains a 
       4th Thursday of the month (repeat_nth_day = 4, repeat_weekday = 5) 
      */ 
      /* 
      (`repeat_nth_day` IS NULL OR `repeat_nth_day` = _nth_day) AND 
      (`repeat_weekday` IS NULL OR `repeat_weekday` = _weekday) 
      */ 
     ) 
    GROUP BY e.`event_id`; 

END 

這可能嗎?
建議?
謝謝。

編輯看起來像我的大多數嘗試是有缺陷的..

`date_start` : "2016-01-01" 
`date_end` : "2017-12-31" 
`time_start` : NULL, 
`time_end` : NULL, 
`repeat_interval` : NULL 
`repeat_year` : NULL 
`repeat_month` : 2 
`repeat_day` : 1 
`repeat_nth_day` : NULL 
`repeat_weekday` : NULL 

重複每2月1日 但是它會在範圍2016年2月15日得到恢復 - 2016年3月15日,因爲範圍包括2月,包括第一(3月)...但不包括2月1日...... :(

回答

0

我想出了一個解決方案,涉及2件事情,我沒有很多經驗與:臨時表和循環。我遍歷日期範圍並將每個日期結果插入臨時表

REPEAT 
    # events_filter_date inserts into the temporary table 
    CALL events_filter_date(_date_cur); 
    SET _date_cur = DATE_ADD(_date_cur, INTERVAL 1 DAY); 
UNTIL _date_cur > _date_end 
END REPEAT; 

SELECT * 
FROM `events_temp`;