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

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

or

赛事B从2011年3月1日开始,每两周在周二举行一次

我如何将其存储在数据库中,使其易于查找。如果有大量的事件,我不希望出现性能问题,而且在呈现日历时,我必须遍历每一个事件。


当前回答

对于所有对此感兴趣的人,现在你可以在几分钟内复制粘贴开始学习。我尽可能地采纳了评论中的建议。如果我遗漏了什么,请告诉我。

“复杂版”:

事件

+----------+----------------+
| 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');

也可以作为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 />";
    }
?>

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 设置这个需要几分钟。没有时间。:)

其他回答

存储“简单”重复模式

对于我的基于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_interval。表格看起来是这样的:

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)。

希望这也能帮助到其他人!


存储“复杂”重复模式

此方法更适合存储复杂的模式,例如

事件A从2011年3月3日开始,每月3日重复发生

or

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

我建议将此系统与上述系统相结合,以获得最大的灵活性。这个表应该是这样的:

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之间。在星期几中重复_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

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

我专门为这种情况开发了一种深奥的编程语言。它最好的部分是它是无模式的和平台独立的。你只需要为你的时间表写一个选择器程序,它的语法受到这里描述的一组规则的约束

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

这些规则是可扩展的,您可以根据想要执行的重复逻辑类型添加任何类型的自定义,而不用担心模式迁移等问题。

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

套@dat_ini = 2023-05-20’,@dat_fim = 2022-11-20’; 选择(DATEDIFF(@dat_fim,@dat_ini) % 60

这个< 10

它只在短时间内有效。

要做到这一点,请使用开始日期并更改屏幕上的月份并添加一年,然后从开始日期中减去它,然后就可以工作了。

为什么不使用类似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)]

希望这能有所帮助。

@Rogue编码器

这太棒了!

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

而不是:

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

Do:

$current_timestamp = 1299132000 ;

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

为了更进一步,我们可以把那些不会永远重复的事件也包括进来。

可以添加“repeat_interval_1_end”之类的内容,以表示最后一个“repeat_interval_1”的日期。然而,这使得查询更复杂,我真的不知道如何做到这一点…

也许有人可以帮忙!