我正在尝试构建自己的日历,并且我有一个用于输入以下所示事件的表单。
关于如何设计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
我真的很感激你的建议!
答案 0 :(得分:6)
我认为你不需要任何关系数组。
您的数据库表可能是:
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找到“本月的第3个星期三”是否在特定范围内,我认为即使不是不可能也没有用户定义的函数,但是非常难以且代码不清楚,我建议你把你所有的事件,在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;
因此,在提取时,您可以测试记录是否符合以下条件:
如果您的记录不符合条件,请从阵列中删除它,而不是数据库: - )。
将一些任务放入SQL查询也很容易。例如,您可以按SELECT ... WHERE ... OR ... (start LIKE '%-:month-:day %)
过滤特定月 - 日的重复事件(假设事件的开始和结束相同,如问题中的图片所示)。这是DATETIME
字段的一些优点,您可以轻松地搜索它们,就像它们是字符串一样,因此%-12-21 %
查找具有第12个月和第21天的所有记录(它们应始终为两位数字,课程)。 (TIMESTAMP
的优点是很容易计算日期差异等。)
如果事件在每个工作日重复,请使用... LIKE
% - % - :day%`,依此类推。
所以最后你需要两个返回boolean
的函数来检查两种情况:
您甚至可以使用foreach
或其他东西蛮力对其进行编码。
此外,如果字段值为127,则无需执行工作日检查 - 因此它每天都会发生。
2013-03-29编辑 - 解释如何将比特用作工作日
在字段weekdays
中,可以将天数保留为比特,因为在一个(无符号)INT(1)数字中有7天和8位。因此,您不必添加更多列,例如“occurrence _monday”,“happen_tuesday”等,也不必使用任何关系。我这样做是因为我认为事件可能发生在“每个星期一”和“每个星期五”。如果没有,请在那里保留一个数字(0 =星期日,1 =星期一等)。
此外,每天发生的事件也是每周7天发生的事件的特例,因此我在ENUM
列中不需要另外reoccurring
值。
现在如何在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的星期几数字,这就是我使用它的原因。
答案 1 :(得分:2)
之前我已解决过这个问题而且很复杂。但是,我确实想出了一个我觉得有意义的解决方案(在堆栈溢出的帮助下)。我最后做的是创建事件表和事件重复表。活动表全权负责举办个人活动。事件重复表负责保存重复实例。
然后我使用事件重复表来生成进入事件表的事件。这允许的是显示事件的简化方法。这是因为已经为您完成了重复计算。创建重复时,我会生成两年的事件。如果用户在该两年范围之外导航,那么我会根据需要生成更多事件。
每晚都会运行一个cron作业,以确保每次重复都会生成至少两年的事件。然后,每次重复都需要生成直到字段,结束重复,重复类型以及标题,描述,时间等信息。
如果你决定走这条路,需要代码示例,请务必告诉我。