我正在尝试构建自己的日历,并且我有一个用于输入事件的表单,如下所示。
我对如何设计 MySQL 表以及如何以易于提取的方式记录这些信息感到困惑?
以下是我的尝试,它有效但真的很丑,我相信它可以改进:
CREATE TABLE events (
event_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
location VARCHAR(100) NOT NULL,
body TEXT NOT NULL,
start_ts DATETIME NOT NULL,
end_ts DATETIME NOT NULL,
valid ENUM('Y','N') DEFAULT 'Y',
reoccurring ENUM('Y','N') DEFAULT 'N',
every ENUM('day','week','month','year',''),
bymonth ENUM('day','weekday',''),
end_date DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (event_id)
);
CREATE TABLE events_byweek (
event_id INTEGER UNSIGNED NOT NULL,
weekday TINYINT UNSIGNED NOT NULL,
FOREIGN KEY (event_id)
REFERENCES events(event_id)
);
-- returns all dates, reoccurring or otherwise within specified time range
-- by month
SELECT * FROM events WHERE (:year = YEAR(start_ts) AND :month = MONTH(start_ts))
OR (reoccurring = 'Y' AND ((YEAR(end_date) >= :year AND MONTH(end_date) >= :month) OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'
-- by day
SELECT * FROM events WHERE DATE(start_ts) = :date
OR (reoccurring = 'Y' AND (DATE(end_date) >= :date OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'
-- by week
SELECT * FROM events_byweek WHERE event_id = :event_id
如果有任何建议,我将不胜感激!
最佳答案
我认为你不需要任何关系数组。
您的数据库表可能是:
event_id UINT(10) auto_increment NOT NULL
/* not important fields omitted */
start DATETIME not null
end DATETIME not null
reoccurring ENUM('NO', 'WEEKDAY', 'MONTHDAY', 'MONTH_REL', 'YEARLY') DEFAULT 'NO';
weekdays UINT(1) DEFAULT 0;
until TIMESTAMP DEFAULT NULL
我使用了DATETIME
,因为它更容易查询(见下文),但实际上这无关紧要。如果您愿意,可以保留它 TIMESTAMP
。
在 weekdays
中,可以保留一个字节,其位为 2^0
:星期日,2^1
:星期一,依此类推。因此,如果事件每天都重复发生,您将在那里放置 127
。
如果 until
为 NULL
,则事件将永远重复。
因为很难通过SQL找到“每月的第三个星期三”是否在特定范围内,我认为没有用户自定义函数即使不是不可能,但很难而且代码不会清晰,我建议您获取所有事件,在 php 中获取它们并在那里过滤。
仅加载必要事件(预过滤)的查询将是:
SELECT ... FROM events
WHERE
/* We take all non-reoccuring events in period */
((reoccurring = 'NO') AND (start >= :start) AND (end <= :end))
OR
/* We take some part of reoccurring events */
((reoccuring <> 'NO') AND ((start <= :end) OR (end >= :start)) AND ((until >= :start) OR until IS NULL)
ORDER BY start ASC;
因此,在获取记录时,您可以测试一条记录是否满足以下条件:
- 没有重复出现,所以保留
- 在特定的工作日重复发生(例如每周三和每个周五)- 检查期间之间是否有这样的工作日,如果至少有一个,则事件保留,否则,将其丢弃
- 如果重复发生在月日(例如,每年 12 月 21 日),则执行相同的操作。
- 如果每月重复一次 - 相对日期(如第三个星期三),则执行相同操作 等
如果你的记录不符合条件,删除它,当然是从数组中,而不是数据库:-)。
一些任务也很容易放入 SQL 查询中。例如,您可以通过 SELECT ... WHERE ... OR ... (start LIKE '%-:month-:day %)
过滤特定月日的重复事件(假设开始和事件结束是相同的,如问题中的图片所示)。这是 DATETIME
字段的一些优势,您可以像搜索字符串一样轻松地搜索它们,因此 %-12-21 %
会找到包含第 12 个月和第 21 天的所有记录(当然,它们应该始终是两位数)。 (TIMESTAMP
的优点是很容易计算日期差异等)
如果事件在每个月的每一天都重复,请使用 ... LIKE
%-%-:day %` 等等。
所以最后你需要两个返回 boolean
的函数来检查两种情况:
- 是特定时期的工作日
- 是该期间的第 n 个工作日(请注意,如果第一个失败,则不需要运行第二个)
您甚至可以使用 foreach
或其他东西通过蛮力对它们进行编码。
此外,如果字段值为 127,则无需执行工作日检查 - 因此它每天都会发生。
2013-03-29 编辑 - 说明如何将位用作工作日
在字段 weekdays
中,可以将天数保留为位,因为一个(无符号)INT(1) 数中有 7 天和 8 位。因此,您不必添加更多列,如“occurs_monday”、“occurs_tuesday”等,也不必使用任何关系。之所以这样提出,是因为我觉得“每周一”、“每周五”都有可能发生事件。如果不是,请在此处保留一个数字(0=星期日,1=星期一,依此类推)。
此外,每天发生的事件也是每周 7 天发生的事件的特例,所以我不需要 reoccurring
中的另一个 ENUM
值> 专栏。
现在如何在 PHP 中使用它?
您只需要测试是否设置了特定工作日的位。您可以使用按位 AND
运算符执行此操作。如果您定义一些常量,它会更简单:
define("WEEKDAY_SUNDAY",1); // 2^0
define("WEEKDAY_MONDAY",2); // 2^1
define("WEEKDAY_TUESDAY",4); // 2^2
// ....
define("WEEKDAY_SATURDAY",64); // 2^6
// Does the event occur on Friday, maybe also other weekdays?
if($row['weekdays'] & WEEKDAY_FRIDAY){
// Does the event occurs only on Friday, and no other day?
if($row['weekdays'] == WEEKDAY_FRIDAY){
// Let's make the event occur on Friday and the day(s) that it already occurs
$row['weekdays'] = $row['weekdays'] | WEEKDAY_FRIDAY;
// Make the event occur only on Friday and no other weekday
$row['weekdays'] = WEEKDAY_FRIDAY;
// Let's check if the event occurs today:
if(pow(2,date('w')) & $row['weekdays']){ //...
带“w”参数的 date
函数返回从 0 到 6 的星期几,所以这就是我使用它的原因。
关于php - 周期性日历事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14866343/