日历重复/重复事件 - 最佳存储方法

Posted

技术标签:

【中文标题】日历重复/重复事件 - 最佳存储方法【英文标题】:Calendar Recurring/Repeating Events - Best Storage Method 【发布时间】:2011-07-08 04:55:21 【问题描述】:

我正在构建一个自定义事件系统,如果你有一个看起来像这样的重复事件:

从 2011 年 3 月 3 日起,事件 A 每 4 天重复一次

从 2011 年 3 月 1 日开始,事件 B 每 2 周在星期二重复一次

如何以一种易于查找的方式将其存储在数据库中。如果有大量事件,我不希望出现性能问题,并且在渲染日历时我必须经过每一个。

【问题讨论】:

你能解释一下为什么1299132000 是硬编码的吗?如果我需要获取给定结束日期的发生日期和用户,该怎么办? @Murali Boy 这是旧的,但我很确定 1299132000 应该是当前日期。 @BrandonWamoldt,我用 SQL Server 尝试了你的想法。 ***.com/questions/20286332/display-next-event-date。我想找到所有下一个项目,如c# version 【参考方案1】:

存储“简单”重复模式

对于我的基于 php/mysql 的日历,我想尽可能高效地存储重复/重复事件信息。我不想有大量的行,我想轻松查找在特定日期发生的所有事件。

以下方法非常适合存储定期出现的重复信息,例如每天、每 n 天、每周、每年每个月等。这包括每周二和星期四类型的模式也是如此,因为它们分别存储为从星期二开始的每周和从星期四开始的每周。

假设我有两张桌子,一张叫events,如下所示:

ID    NAME
1     Sample Event
2     Another Event

还有一个名为events_meta 的表,如下所示:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1299132000
2     1             repeat_interval_1  432000

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

然后,如果您有一个每天循环的日历,获取当天的事件,查询将如下所示:

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

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

还有一个名为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

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

这与上述方法相结合可以涵盖大多数重复/重复的事件模式。如果我遗漏了什么,请发表评论。

【讨论】:

我正在尝试您的存储“简单”重复模式。如果我需要它在每周二重复,我是否需要修改 repeat_start 或创建一个包含最后日期的新记录。或者有没有办法根据第一个repeat_start每周重复一次??? 您的回答对@roguecoder 很有帮助,但它并没有完全奏效......我在这篇文章之后找到了答案:***.com/questions/10545869/… AND ( ( CASE ( 1299132000 - EM1.meta_value ) WHEN 0 THEN 1 ELSE ( 1299132000 - EM1.meta_value) END ) / EM2.meta_value ) = 1 这个/ EM2.meta_value 放错了吗? 这是一个很大的帮助。您如何建议将这些作为单独的记录进行访问,例如,如果您想对个别事件进行 cmets 或签到? 值得注意的是,您不应该对重复间隔使用硬编码值,即一天中的 86400 秒,因为它不考虑夏令时。动态计算这些东西更合适,而是存储interval = dailyinterval_count = 1interval = monthlyinterval_count = 1【参考方案2】:

虽然当前接受的答案对我有很大帮助,但我想分享一些有用的修改,以简化查询并提高性能。


“简单”重复事件

处理定期重复发生的事件,例如:

Repeat every other day 

Repeat every week on Tuesday 

您应该创建两个表,其中一个名为 events,如下所示:

ID    NAME
1     Sample Event
2     Another Event

还有一个名为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

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 )

只需用 unix 时间戳 (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

如果您运行 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 |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+

上面有 1 个连接的解决方案不需要这样的缓冲区。


“复杂”模式

您可以添加对更复杂类型的支持以支持这些类型的重复规则:

Event A repeats every month on the 3rd of the month starting on March 3, 2011

Event A repeats second Friday of the month starting on March 11, 2011

您的事件表可能看起来完全一样:

ID    NAME
1     Sample Event
2     Another Event

然后添加对这些复杂规则的支持,将列添加到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    

请注意,您只需指定repeat_interval一组repeat_yearrepeat_monthrepeat_dayrepeat_weekrepeat_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
  )

这会返回在第 2 周的星期五重复的所有事件,以及每个星期五重复的所有事件,因此它返回事件 ID 1 和 2:

ID    NAME
1     Sample Event
2     Another Event

*在上面的 SQL 中我使用了PHP Date's 默认的工作日索引,所以“5”代表星期五


希望这对其他人的帮助与原始答案对我的帮助一样多!

【讨论】:

这太棒了,谢谢!你知道如何编码“每 2 个月的第一个星期一”或“每 3 个月的第一个星期一”等吗? 我同意这太棒了。然而,我遇到了和乔丹列夫一样的困境。 repeat_interval 字段不适合重复月份,因为有些月份比其他月份长。此外,您如何限制重复事件的持续时间。即,每 2 个月的第一个星期一,持续 8 个月。表格应该有某种结束日期。 这是一个很好的答案。我已经删除了 repeat_interval 并添加了 repeat_end 日期,但这个答案有很大帮助。 提示:对于复杂的模式,可以去掉repeat_interval列,在后面的列中表示(即repeat_year等)。对于第一行,以后每周一重复的情况2013 年 5 月 20 日,可以通过在 repeat_weekday 中放置 1 和在其他列中放置 * 来表示。 @OlivierMATROT @milos 这个想法是设置你想要明确修复的字段,其余的设置为通配符*。因此,对于“每月 3 日”,您只需将 repeat_day 设置为 3,repeat 的其余字段设置为 *(将 repeat_interval 保留为空),并将 repeat_start 设置为 2011 年 3 月 3 日的 unix 时间码成为你的锚定日期。【参考方案3】:

增强:将时间戳替换为日期

作为对随后由 ahoffner 改进的已接受答案的一个小改进 - 可以使用日期格式而不是时间戳。优点是:

    数据库中的可读日期 年份没有问题 > 2038 和时间戳 删除时需要小心基于季节性调整日期的时间戳,即在英国,6 月 28 日比 12 月 28 日早一小时,因此从日期派生时间戳可能会破坏递归算法。

为此,将 DB repeat_start 更改为存储为“日期”类型,repeat_interval 现在保留天数而不是秒数。即 7 重复 7 天。

更改sql行:

WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )

到:

WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)

其他一切都保持不变。简单!

【讨论】:

如果我希望我的活动每年重复一次怎么办? repeat_interval 应该存储 365 天?如果他们一年有 366 天呢? @George02 如果事件是年度的,您将 repeat_interval 保留为 NULL 并且 repeat_year 是 * 然后根据重复的频率,您可以设置 repeat_month 和 repeat_day 例如 3 月 11 日或 repeat_month、repeat_week 和 repeat_weekday 设置为 4 月的第二个星期二.【参考方案4】:

我会遵循本指南: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md

还要确保您使用 iCal 格式,以免重新发明***和 记住 规则#0: 不要将单个重复事件实例作为行存储在数据库中!

【讨论】:

您将如何建模跟踪参加过特定实例的用户?在这种情况下,打破规则 #0 是否有意义? @DannySullivan 从我的脑海中,我会有另一个实体 attendedEventbaseInstanceIdinstanceStartDate - 例如,您创建循环规则日历视图的基本事件并使用开始日期来指定有关该特定实例的信息然后该实体也可能具有类似attendedListId 的内容,这会导致另一个表idattendedUserId @DannySullivan 我知道你已经有一段时间没有问过了。但是在前面的评论之外,您总是可以进行反向查找以查看该用户是否是事件重复模式的一部分。这会告诉你他们是否至少被安排参加活动。他们是否真的参加是另一回事,这更像是丹尼沙利文的评论。【参考方案5】:

对于所有对此感兴趣的人,现在您只需复制和粘贴即可在几分钟内开始使用。我尽可能地接受了 cmets 的建议。如果我遗漏了什么,请告诉我。

“复杂版本”:

事件

+----------+----------------+ |身份证 |姓名 | +----------+----------------+ | 1 |示例事件 1 | | 2 |第二场 | | 3 |第三场 | +----------+----------------+

events_meta

+----+----------+--------------+------------------ +-------------+-------------+------------+------- ------+----------------+ |身份证 | event_id |重复开始 |重复间隔 |重复年 |重复月 |重复日 |重复周|重复工作日 | +----+----------+--------------+------------------ +-------------+-------------+------------+------- ------+----------------+ | 1 | 1 | 2014-07-04 | 7 |空 |空 |空 |空 |空 | | 2 | 2 | 2014-06-26 |空 | 2014 | * | * | 2 | 5 | | 3 | 3 | 2014-07-04 |空 | * | * | * | * | 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');

也可以使用MySQL export(方便访问)

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 />";
    
?>

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
// ----------------------------------------------------------------------------------------------------
?>

这里还提供了 php 代码(为了提高可读性):index.php 和connect.php 现在设置它应该需要几分钟。不是小时。 :)

【讨论】:

如何查询以获取某个日期范围内的所有重复事件。即获取 2014 年 10 月 1 日至 2014 年 12 月 30 日之间的所有重复事件。谢谢你的帖子 @Wellwisher - 重复...直到临时表***.com/questions/34407833/… @Alex 如何从重复事件中删除一个实例。 我知道这是一个旧线程,但为什么在 repeat_* 列上使用 varchar 类型?你不能用一个整数和一个负值来代替'*'吗? 感谢您的代码。但是,我必须指出,您的 db/queries 实现有点令人不安,而且效率很低。例如,为什么对这些简单的列使用 varchar(255) (正如@OlivierMATROT 提到的,您可以使用整数,即使不是,为什么要使用 255?)。如果你重复查询 30 次,为什么不使用语句或过程呢?只是说如果有人要实现这一点。【参考方案6】:

为什么不使用类似于 Apache cron 作业的机制? http://en.wikipedia.org/wiki/Cron

对于日历\计划,我会使用稍微不同的“位”值来适应标准日历重复事件 - 而不是 [星期几 (0 - 7)、月份 (1 - 12)、月份日期 (1 - 31)、小时 (0 - 23)、分钟 (0 - 59)]

-- 我会使用类似的东西 [年(每N年重复一次),月(1 - 12),月中的日子(1 - 31),月中的周(1-5),周中的日(0 - 7)]

希望这会有所帮助。

【讨论】:

我认为这是太多的星期选项。 1-7 或 0-6 似乎更准确。 用cron来存储repeat就好了。但问题是查找起来非常困难。 @Vladimir 你如何存储,每两个星期二(每两周星期二) @julestruong 这个网站看起来有答案:coderwall.com/p/yzzu5a/running-a-cron-job-every-other-week cron 的表达能力有限,因为它是无状态的(仅将当前/假设的日期/时间与模式进行比较),因此,它不能代表某些常见的业务/人类模式,例如“每三天”或“每 7 小时”,这需要记住最后一次出现。这并不明显;您可能认为您只是在 crontab 中说 day/3 或 hour/7,但是在月/日结束时,您的“剩余”天/小时少于 3 或 7;可能造成灾难性后果。【参考方案7】:

虽然建议的解决方案有效,但我尝试使用完整日历来实现,并且每个视图都需要 90 多个数据库调用(因为它会加载当前、上个月和下个月),对此我并不太兴奋.

我找到了一个递归库https://github.com/tplaner/When,您只需将规则存储在数据库中,然后一个查询即可提取所有相关规则。

希望这对其他人有所帮助,因为我花了很多时间试图找到一个好的解决方案。

编辑:此库适用于 PHP

【讨论】:

我也想使用全日历。什么时候图书馆可以帮助我?如何提取合适的事件? @piernik - 我会按照文档中的方式设置库,如果您遇到特定问题,请使用您设置的代码和遇到的问题在 *** 上打开一个新问题。我敢肯定,如果您在某些成员身上付出这么多的努力,将会对您有所帮助。 我的意思是使用When 您必须将所有重复日期存储在数据库中,或者获取所有重复事件并在数据库中的php no 中生成日期。我说的对吗? @piernik 您将在数据库中存储初始日期和规则并使用When 生成所有日期 - 这些日期是从初始存储的日期/规则中填充的。 这也不好 - 你不能在单个 mysql 命令中获得正确的事件 - 你必须使用 PHP。还是谢谢【参考方案8】:

RRULE 标准正是为满足这一要求而构建的,即保存和理解重复。微软和谷歌都在他们的日历活动中使用它。请浏览此文档以了解更多详细信息。 https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html

【讨论】:

【参考方案9】:

我专门为这种情况开发了一种深奥的编程语言。关于它的最好的部分是它没有模式并且独立于平台。您只需要为您的日程安排编写一个选择器程序,其语法受到此处描述的规则集的约束 -

https://github.com/tusharmath/sheql/wiki/Rules

规则是可扩展的,您可以根据要执行的重复逻辑类型添加任何类型的自定义,而无需担心架构迁移等。

这是一种完全不同的方法,可能有其自身的一些缺点。

【讨论】:

【参考方案10】:

听起来很像存储在系统表中的 MySQL 事件。您可以查看结构并找出不需要哪些列:

   EVENT_CATALOG: NULL
    EVENT_SCHEMA: myschema
      EVENT_NAME: e_store_ts
         DEFINER: jon@ghidora
      EVENT_BODY: SQL
EVENT_DEFINITION: INSERT INTO myschema.mytable VALUES (UNIX_TIMESTAMP())
      EVENT_TYPE: RECURRING
      EXECUTE_AT: NULL
  INTERVAL_VALUE: 5
  INTERVAL_FIELD: SECOND
        SQL_MODE: NULL
          STARTS: 0000-00-00 00:00:00
            ENDS: 0000-00-00 00:00:00
          STATUS: ENABLED
   ON_COMPLETION: NOT PRESERVE
         CREATED: 2006-02-09 22:36:06
    LAST_ALTERED: 2006-02-09 22:36:06
   LAST_EXECUTED: NULL
   EVENT_COMMENT:

【讨论】:

【参考方案11】:

@流氓程序员

这太棒了!

您可以简单地使用模运算(mysql 中的 MOD 或 %)使您的代码在最后变得简单:

代替:

AND (
    ( CASE ( 1299132000 - EM1.`meta_value` )
        WHEN 0
          THEN 1
        ELSE ( 1299132000 - EM1.`meta_value` )
      END
    ) / EM2.`meta_value`
) = 1

做:

$current_timestamp = 1299132000 ;

AND ( ('$current_timestamp' - EM1.`meta_value` ) MOD EM2.`meta_value`) = 1")

为了更进一步,可以包括不会永远重复的事件。

可以添加类似“repeat_interval_1_end”来表示最后一个“repeat_interval_1”的日期。然而,这使得查询更加复杂,我真的不知道该怎么做......

也许有人可以帮忙!

【讨论】:

【参考方案12】:

你给出的两个例子非常简单;它们可以表示为一个简单的间隔(第一个是四天,第二个是 14 天)。你如何建模这将完全取决于你的重复的复杂性。如果您上面的内容真的那么简单,那么请存储开始日期和重复间隔中的天数。

但是,如果您需要支持

从 2011 年 3 月 3 日开始,每个月的 3 日,事件 A 都会重复

或者

事件 A 从 2011 年 3 月 11 日开始重复每月的第二个星期五

那么这是一个更复杂的模式。

【讨论】:

我添加了您稍后提到的更复杂的规则,但暂时没有。我将如何建模 SQL 查询以获取 2011 年 3 月 7 日的事件,以便它可以获取我的重复事件?

以上是关于日历重复/重复事件 - 最佳存储方法的主要内容,如果未能解决你的问题,请参考以下文章

在数据库中表示“重复事件”的最佳方式是啥?

是否有存储和解析日历日期重复字符串的标准?

在构建日历应用程序时,我应该在我的数据库中存储日期或重复规则吗?

重复性事件的数据库设计与异常

从电子表格创建 Google 日历事件但防止重复

重复事件、SQL 查询