9

我正在尝试构建自己的日历,并且我有一个用于输入事件的表单,如下所示。

我对如何设计 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

我真的很感激任何建议,拜托!

4

2 回答 2

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

如果untilNULL,则事件将永远重复。

因为通过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;

因此,在获取时,您可以测试记录是否满足以下条件:

  1. 不会再次发生,所以保留它
  2. 在特定的工作日重复发生(例如每周三和每周五) - 检查期间之间是否有这样的工作日,如果至少有一个,则事件保留,否则,将其丢弃
  3. 如果重复发生在月日(例如,每 12 月 21 日),请执行相同操作。
  4. 如果每月重复发生 - 相对日期(如第三个星期三),请执行相同操作等。

如果您的记录不符合条件,请从数组中删除它,当然不是从数据库 :-)。

一些任务也很容易放入 SQL 查询中。例如,您可以过滤特定月份的重复事件SELECT ... WHERE ... OR ... (start LIKE '%-:month-:day %)(假设事件的开始和结束是相同的,如问题中的图片所示)。这是该DATETIME字段的一些优点,您可以像搜索字符串一样轻松地搜索它们,因此%-12-21 %可以找到包含第 12 个月和第 21 天的所有记录(当然,它们应该始终是两位数)。(优点TIMESTAMP是很容易计算日期差异等)

如果事件在每个月重复,使用... LIKE%-%-:day %` 等等。

所以最后你需要两个函数返回boolean来检查两种情况:

  1. 是特定时期的工作日
  2. 是该期间每月的第 n 个工作日(请注意,如果第一个失败,则不需要运行第二个)

foreach您甚至可以通过使用或其他东西通过蛮力对它们进行编码。

此外,如果字段值为 127,您不需要执行工作日检查 - 所以它每天都会发生。

编辑于 2013-03-29 - 解释如何使用比特作为工作日

在该字段weekdays中,可以将天保留为位,因为一个(无符号)INT(1) 数字中有 7 天和 8 位。因此,您不必添加更多列,如“occurs_monday”、“occurs_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 的星期几,所以这就是我使用它的原因。

于 2013-03-25T18:46:14.270 回答
2

我以前解决过这个问题,它很复杂。但是,我确实提出了一个我认为有意义的解决方案(借助堆栈溢出)。我最终要做的是创建一个事件表和一个事件重复表。事件表全权负责举办个别事件。事件重复表负责保存重复实例。

然后我要做的是使用事件重复表来生成进入事件表的事件。这允许的是一种显示事件的简化方式。这是因为已经为您完成了重复计算。创建重复时,我会生成两年的事件。如果用户在该两年范围之外导航,那么我会根据需要生成更多事件。

还有一个每天晚上运行的 cron 作业,以确保每次重复生成至少两年的事件。然后,每个重复都需要生成一个直到字段、结束重复、重复类型以及标题、描述、时间等信息。

如果你决定走这条路,并且需要代码示例,一定要告诉我。

于 2013-03-26T16:16:53.153 回答