Bra*_*ldt 291 database-design calendar
我正在构建一个自定义事件系统,如果你有一个如下所示的重复事件:
活动A从2011年3月3日起每4天重复一次
要么
活动B于2011年3月1日星期二每2周重复一次
如何以一种易于查找的方式将其存储在数据库中.如果有大量事件,我不希望出现性能问题,在渲染日历时我必须经历每一个事件.
Bra*_*ldt 197
对于基于PHP/MySQL的日历,我希望尽可能高效地存储重复/重复的事件信息.我不想拥有大量的行,我想轻松查找将在特定日期发生的所有事件.
下面的方法非常适合存储定期发生的重复信息,例如每天,每n天,每周,每年每月等等.这包括每周二和周四的类型模式,因为它们是存储的每周从星期二开始,每周从星期四开始.
假设我有两个表,一个events
像这样调用:
ID NAME
1 Sample Event
2 Another Event
Run Code Online (Sandbox Code Playgroud)
还有一个events_meta
像这样的表:
ID event_id meta_key meta_value
1 1 repeat_start 1299132000
2 1 repeat_interval_1 432000
Run Code Online (Sandbox Code Playgroud)
假设repeat_start是没有时间作为unix时间戳的日期,repeat_interval是间隔之间的秒数(432000是5天).
repeat_interval_1与ID 1的repeat_start一起使用.因此,如果我的事件在每个星期二和每个星期四重复,则repeat_interval将为604800(7天),并且将有2个repeat_starts和2个repeat_intervals.该表如下所示:
ID event_id meta_key meta_value
1 1 repeat_start 1298959200 -- This is for the Tuesday repeat
2 1 repeat_interval_1 604800
3 1 repeat_start 1299132000 -- This is for the Thursday repeat
4 1 repeat_interval_3 604800
5 2 repeat_start 1299132000
6 2 repeat_interval_5 1 -- Using 1 as a value gives us an event that only happens once
Run Code Online (Sandbox Code Playgroud)
然后,如果你有一个每天循环的日历,抓住当天的事件,查询将如下所示:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
LIMIT 0 , 30
Run Code Online (Sandbox Code Playgroud)
{current_timestamp}
用当前日期的unix时间戳替换(减去时间,因此小时,分钟和秒值将设置为0).
希望这也有助于其他人!
这种方法更适合存储复杂的模式,如
Event A repeats every month on the 3rd of the month starting on March 3, 2011
要么
Event A repeats Friday of the 2nd week of the month starting on March 11, 2011
我建议将其与上述系统相结合,以获得最大的灵活性.这个表应该像:
ID NAME
1 Sample Event
2 Another Event
Run Code Online (Sandbox Code Playgroud)
还有一个events_meta
像这样的表:
ID event_id meta_key meta_value
1 1 repeat_start 1299132000 -- March 3rd, 2011
2 1 repeat_year_1 *
3 1 repeat_month_1 *
4 1 repeat_week_im_1 2
5 1 repeat_weekday_1 6
Run Code Online (Sandbox Code Playgroud)
repeat_week_im
表示当月的周,可能在1到5之间.repeat_weekday
在一周的这一天,1-7.
现在假设您在日历/周中循环以在日历中创建月视图,您可以编写如下查询:
SELECT EV . *
FROM `events` AS EV
JOIN `events_meta` EM1 ON EM1.event_id = EV.id
AND EM1.meta_key = 'repeat_start'
LEFT JOIN `events_meta` EM2 ON EM2.meta_key = CONCAT( 'repeat_year_', EM1.id )
LEFT JOIN `events_meta` EM3 ON EM3.meta_key = CONCAT( 'repeat_month_', EM1.id )
LEFT JOIN `events_meta` EM4 ON EM4.meta_key = CONCAT( 'repeat_week_im_', EM1.id )
LEFT JOIN `events_meta` EM5 ON EM5.meta_key = CONCAT( 'repeat_weekday_', EM1.id )
WHERE (
EM2.meta_value =2011
OR EM2.meta_value = '*'
)
AND (
EM3.meta_value =4
OR EM3.meta_value = '*'
)
AND (
EM4.meta_value =2
OR EM4.meta_value = '*'
)
AND (
EM5.meta_value =6
OR EM5.meta_value = '*'
)
AND EM1.meta_value >= {current_timestamp}
LIMIT 0 , 30
Run Code Online (Sandbox Code Playgroud)
结合上述方法可以组合以覆盖大多数重复/重复发生的事件模式.如果我错过了什么,请发表评论.
aho*_*ner 175
虽然目前接受的答案对我来说是一个巨大的帮助,但我想分享一些有用的修改,以简化查询并提高性能.
处理定期重复的事件,例如:
Repeat every other day
Run Code Online (Sandbox Code Playgroud)
要么
Repeat every week on Tuesday
Run Code Online (Sandbox Code Playgroud)
你应该创建两个表,一个events
像这样调用:
ID NAME
1 Sample Event
2 Another Event
Run Code Online (Sandbox Code Playgroud)
还有一个events_meta
像这样的表:
ID event_id repeat_start repeat_interval
1 1 1369008000 604800 -- Repeats every Monday after May 20th 2013
1 1 1369008000 604800 -- Also repeats every Friday after May 20th 2013
Run Code Online (Sandbox Code Playgroud)
与repeat_start
作为一个Unix时间戳的日期没有时间(1369008000对应于2013年5月20日),并repeat_interval
在几秒钟的时间间隔之间的量(604800是7天).
通过循环日历中的每一天,您可以使用此简单查询获得重复事件:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1299736800 - repeat_start) % repeat_interval = 0 )
Run Code Online (Sandbox Code Playgroud)
只需在日历中的每个日期替换unix-timestamp(1299736800)即可.
注意使用模数(%符号).此符号类似于常规除法,但返回"余数"而不是商,因此只要当前日期是repeat_start的repeat_interval的精确倍数,就会返回0.
这明显快于之前建议的基于"meta_keys"的答案,如下所示:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
Run Code Online (Sandbox Code Playgroud)
如果你运行EXPLAIN这个查询,你会注意到它需要使用一个连接缓冲区:
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 1 | SIMPLE | EM1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where |
| 1 | SIMPLE | EV | eq_ref | PRIMARY | PRIMARY | 4 | bcs.EM1.event_id | 1 | |
| 1 | SIMPLE | EM2 | ALL | NULL | NULL | NULL | NULL | 2 | Using where; Using join buffer |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
Run Code Online (Sandbox Code Playgroud)
上面有1个连接的解决方案不需要这样的缓冲区.
您可以添加对更复杂类型的支持,以支持这些类型的重复规则:
Event A repeats every month on the 3rd of the month starting on March 3, 2011
Run Code Online (Sandbox Code Playgroud)
要么
Event A repeats second Friday of the month starting on March 11, 2011
Run Code Online (Sandbox Code Playgroud)
您的事件表看起来完全相同:
ID NAME
1 Sample Event
2 Another Event
Run Code Online (Sandbox Code Playgroud)
然后添加对这些复杂规则的支持,添加列events_meta
如下:
ID event_id repeat_start repeat_interval repeat_year repeat_month repeat_day repeat_week repeat_weekday
1 1 1369008000 604800 NULL NULL NULL NULL NULL -- Repeats every Monday after May 20, 2013
1 1 1368144000 604800 NULL NULL NULL NULL NULL -- Repeats every Friday after May 10, 2013
2 2 1369008000 NULL 2013 * * 2 5 -- Repeats on Friday of the 2nd week in every month
Run Code Online (Sandbox Code Playgroud)
请注意,您只需要要么指定一个repeat_interval
或一组repeat_year
,repeat_month
,repeat_day
,repeat_week
,和repeat_weekday
数据.
这使得同时选择两种类型非常简单.只需遍历每一天并填写正确的值(2013年6月7日为1370563200,然后是年,月,日,周数和工作日,如下所示):
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
OR (
(repeat_year = 2013 OR repeat_year = '*' )
AND
(repeat_month = 6 OR repeat_month = '*' )
AND
(repeat_day = 7 OR repeat_day = '*' )
AND
(repeat_week = 2 OR repeat_week = '*' )
AND
(repeat_weekday = 5 OR repeat_weekday = '*' )
AND repeat_start <= 1370563200
)
Run Code Online (Sandbox Code Playgroud)
这将返回在第二周的星期五重复的所有事件,以及每周五重复的任何事件,因此它返回事件ID 1和2:
ID NAME
1 Sample Event
2 Another Event
Run Code Online (Sandbox Code Playgroud)
*上面SQL中的Sidenote我使用了PHP Date的默认工作日索引,所以周五为"5"
希望这能帮助别人,就像原来的答案帮助我一样!
小智 25
作为随后由ahoffner提炼的已接受答案的一个小改进 - 可以使用日期格式而不是时间戳.优点是:
要执行此操作,请将repeat_start
要存储的数据库更改为"日期"类型,repeat_interval
现在可以保留数天而不是秒.即7天重复7天.
更改sql行:
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
Run Code Online (Sandbox Code Playgroud)
至:
WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)
Run Code Online (Sandbox Code Playgroud)
其他一切都是一样的.Simples!
Ale*_*lex 24
对于所有对此感兴趣的人,现在您只需复制并粘贴即可在几分钟内开始使用.我尽可能地在评论中接受了建议.如果我遗失了某些东西,请告诉我.
"复杂版本":
事件
+----------+----------------+ | ID | NAME | +----------+----------------+ | 1 | Sample event 1 | | 2 | Second event | | 3 | Third event | +----------+----------------+
events_meta
+----+----------+--------------+------------------+-------------+--------------+------------+-------------+----------------+ | ID | event_id | repeat_start | repeat_interval | repeat_year | repeat_month | repeat_day | repeat_week | repeat_weekday | +----+----------+--------------+------------------+-------------+--------------+------------+-------------+----------------+ | 1 | 1 | 2014-07-04 | 7 | NULL | NULL | NULL | NULL | NULL | | 2 | 2 | 2014-06-26 | NULL | 2014 | * | * | 2 | 5 | | 3 | 3 | 2014-07-04 | NULL | * | * | * | * | 5 | +----+----------+--------------+------------------+-------------+--------------+------------+-------------+----------------+
SQL代码:
CREATE TABLE IF NOT EXISTS `events` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`NAME` varchar(255) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
--
-- Dumping data for table `events`
--
INSERT INTO `events` (`ID`, `NAME`) VALUES
(1, 'Sample event'),
(2, 'Another event'),
(3, 'Third event...');
CREATE TABLE IF NOT EXISTS `events_meta` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`event_id` int(11) NOT NULL,
`repeat_start` date NOT NULL,
`repeat_interval` varchar(255) NOT NULL,
`repeat_year` varchar(255) NOT NULL,
`repeat_month` varchar(255) NOT NULL,
`repeat_day` varchar(255) NOT NULL,
`repeat_week` varchar(255) NOT NULL,
`repeat_weekday` varchar(255) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
--
-- Dumping data for table `events_meta`
--
INSERT INTO `events_meta` (`ID`, `event_id`, `repeat_start`, `repeat_interval`, `repeat_year`, `repeat_month`, `repeat_day`, `repeat_week`, `repeat_weekday`) VALUES
(1, 1, '2014-07-04', '7', 'NULL', 'NULL', 'NULL', 'NULL', 'NULL'),
(2, 2, '2014-06-26', 'NULL', '2014', '*', '*', '2', '5'),
(3, 3, '2014-07-04', 'NULL', '*', '*', '*', '*', '1');
Run Code Online (Sandbox Code Playgroud)
也可用作MySQL导出(便于访问)
PHP示例代码index.php:
<?php
require 'connect.php';
$now = strtotime("yesterday");
$pushToFirst = -11;
for($i = $pushToFirst; $i < $pushToFirst+30; $i++)
{
$now = strtotime("+".$i." day");
$year = date("Y", $now);
$month = date("m", $now);
$day = date("d", $now);
$nowString = $year . "-" . $month . "-" . $day;
$week = (int) ((date('d', $now) - 1) / 7) + 1;
$weekday = date("N", $now);
echo $nowString . "<br />";
echo $week . " " . $weekday . "<br />";
$sql = "SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE ( DATEDIFF( '$nowString', repeat_start ) % repeat_interval = 0 )
OR (
(repeat_year = $year OR repeat_year = '*' )
AND
(repeat_month = $month OR repeat_month = '*' )
AND
(repeat_day = $day OR repeat_day = '*' )
AND
(repeat_week = $week OR repeat_week = '*' )
AND
(repeat_weekday = $weekday OR repeat_weekday = '*' )
AND repeat_start <= DATE('$nowString')
)";
foreach ($dbConnect->query($sql) as $row) {
print $row['ID'] . "\t";
print $row['NAME'] . "<br />";
}
echo "<br /><br /><br />";
}
?>
Run Code Online (Sandbox Code Playgroud)
PHP示例代码connect.php:
<?
// ----------------------------------------------------------------------------------------------------
// Connecting to database
// ----------------------------------------------------------------------------------------------------
// Database variables
$username = "";
$password = "";
$hostname = "";
$database = "";
// Try to connect to database and set charset to UTF8
try {
$dbConnect = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);
$dbConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
// ----------------------------------------------------------------------------------------------------
// / Connecting to database
// ----------------------------------------------------------------------------------------------------
?>
Run Code Online (Sandbox Code Playgroud)
此处还提供了php代码(为了更好的可读性):
index.php
和
connect.php
现在设置它应该花费你几分钟.不是几个小时 :)
Gal*_*cha 23
我会遵循这个指南:https: //github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md
还要确保使用iCal格式,以免重新发明轮子并记住规则#0: 不要将单个定期事件实例存储为数据库中的行!
Tim*_*sey 15
虽然提出的解决方案有效,但我试图使用完整日历实现,并且每个视图需要超过90个数据库调用(因为它加载当前,上个月和下个月),我并不太兴奋.
我找到了一个递归库https://github.com/tplaner/当你只是将规则存储在数据库中时,我会找到一个查询来提取所有相关规则.
希望这会帮助其他人,因为我花了这么多时间试图找到一个好的解决方案.
编辑:此库适用于PHP
小智 14
为什么不使用类似于Apache cron作业的机制?http://en.wikipedia.org/wiki/Cron
对于calendar\scheduling,我会使用略微不同的"bits"值来容纳标准日历reoccurence事件 - 而不是[day of week(0 - 7),month(1 - 12),day of month(1 - 31),小时(0 - 23),分钟(0 - 59)]
- 我会使用类似[年份(重复每N年),月份(1 - 12),月份(1 - 31),月份(1-5),星期几(0 - 7)的内容]
希望这可以帮助.
RRULE 标准正是为这个要求而建立的,即保存和理解重复。微软和谷歌都在他们的日历活动中使用它。请仔细阅读此文档以获取更多详细信息。 https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html
我为这种情况开发了一种深奥的编程语言。关于它的最好的部分是它没有模式并且与平台无关。您只需要为您的计划编写一个选择器程序,其语法受此处描述的一组规则的约束 -
https://github.com/tusharmath/sheql/wiki/Rules
这些规则是可扩展的,您可以根据要执行的重复逻辑类型添加任何类型的自定义,而无需担心架构迁移等。
这是一种完全不同的方法,可能有其自身的一些缺点。
归档时间: |
|
查看次数: |
125678 次 |
最近记录: |