33

我将创建一个基于 Mysql 的日历系统,您可以在其中拥有重复模式,让我们说每个星期一永远永远。它还必须涵盖静态/一次性事件。我想知道的是,哪种解决方案对我来说最合乎逻辑(也是最好的)。我有四种方法我想在其中选择。

方法#1

创建一个接受参数fromto. 该函数将创建一个临时表表,该表通过INSERT ... SELECT. 之后它将读取模式表并通过基于from和的周期填充临时表to

从查询将更容易获取数据的角度来看,此解决方案似乎不错,并且它可以无限工作,因为您可以根据加载的月份重新填充表。我很好奇的是,无论何时这可能是一种滞后的方式。

方法#2

JOIN通过子查询和静态日历创建和加入给定的模式。

这似乎很烦人,因为查询会更大,而且可能根本不好(?)。

方法#3

基本上只是INSERT让我们提前一年说的模式。然后我想一个 cron 工作会重新填充以使其始终提前一年。

这是一种简单的方法,但感觉就像存储了很多不需要的数据,它并没有真正提供我所追求的无限。

方法#4(由 Veger 建议)

如果我理解正确,此方法将从另一个查询中获取模式并在执行时创建事件。这与我对方法 #1 的想法类似,我认为创建多行的简单模式。

但是,如果这将在 Mysql 之外实现,我将失去一些我所追求的数据库功能。


我希望你们能理解我的情况,如果你能给出建议并争论为什么它是最好的,或者给出另一种解决方案。

就我个人而言,我最喜欢方法#1,但我很好奇每次调用都重新填充日历表是否滞后。

4

8 回答 8

14

我以前做过这种日历。我发现最好的方法是按照计划 crons 的方式来处理它。因此,在数据库中,为分钟、小时、月中的日、月和周中的日创建一个字段。

对于 6 月和 8 月的每个星期五晚上 10:00 举行的活动,您的条目如下所示

Minute  Hour  DayOfMonth  Month  DayOfWeek
 0       22     *          6,8       5

然后,您可以有一个字段将其标记为一次性事件,该事件将忽略此信息并仅使用开始日期和持续时间。对于最终重复结束的事件(比如每个周末 3 个月),您只需要添加一个结束日期字段。

这将允许您轻松选择它并减少需要存储的数据量。它也简化了您的查询。

我认为不需要创建临时表。要重新选择相关事件,您可以通过日历视图选择它们。如果您的日历视图按月显示,您的选择将类似于:

SELECT Events.* 
FROM Events 
WHERE (Month LIKE '%,'.$current_month.',%' OR Month = '*') 
    AND DATE(StartDate) >= "'.date('Y-m-d', $firstDayOfCurrentMonth).'" 
    AND DATE(EndDate) <= "'.date('Y-m-d', $lastDayOfCurrentMonth).'"

显然,这应该在准备好的声明中。它还假设您在逗号分隔的月份列表中的第一个和最后一个值之前和之后都有一个逗号(即。,2,4,6,)。Month如果您愿意,您还可以在两者之间创建一个表和一个连接表。其余的可以在渲染日历时由 php 解析出来。

如果您显示日历的每周视图,您可以通过以下方式选择:

SELECT Events.* 
FROM Events 
WHERE (DayOfMonth IN ('.implode(',', $days_this_week).','*') 
    AND (Month LIKE '%,'.$current_month.',%' OR Month = '*')) 
    AND DATE(StartDate) >= "'.date('Y-m-d', $firstDayOfCurrentMonth).'" 
    AND DATE(EndDate) <= "'.date('Y-m-d', $lastDayOfCurrentMonth).'"

我还没有测试过这些查询,所以可能有一些混乱的括号或其他东西。但这将是一般的想法。

因此,您可以为正在显示的每一天运行一个选择,也可以为视图选择所有内容(月、周等)并循环遍历每一天的事件。

于 2012-06-13T10:20:55.893 回答
9

I like Veger's solution best .. instead of populating multiple rows you can just populate the pattern. I suggest the crontab format .. it works so well anyway.

You can query all patterns for a given customer when they load the calendar and fill in events based on the pattern. Unless you have like thousands of patterns for a single user this should not be all that slow. It should also be faster than storing a large number of row events for long periods. You will have to select all patterns at once and do some preprocessing but once again, how many patterns do you expect per user? Even 1000 or so should be pretty fast.

于 2012-06-10T22:32:25.090 回答
4

自从我还在 GW Basic 中编程时,我就有了这个想法;-) 不过,当时我选择了选项 #3,就是这样。回顾一下,以及其他一些回应,这将是我目前的解决方案。

表结构

start (datetime)
stop (datetime, nullable)
interval_unit ([hour, day, week, month, year?])
interval_every (1 = every <unit>, 2 every two <units>, etc.)
type ([positive (default), negative]) - will explain later

可选字段:

title
duration

type字段确定如何处理事件:

  1. 积极的; 正常治疗,它显示在日历中
  2. 消极的; 这个事件取消了另一个(例如每周一但不是在 14 日)

辅助查询

此查询将缩小要显示的事件范围:

SELECT * FROM `events`
WHERE `start` >= :start AND (`stop` IS NULL OR `stop` < :stop)

假设您仅按日期查询范围(没有时间组件),则 的值:stop应该比您的范围提前一天。

现在对于您希望处理的各种事件。

单一事件

start = '2012-06-15 09:00:00'
stop = '2012-06-15 09:00:00'
type = 'positive'

事件于 2012 年 6 月 15 日上午 9 点发生一次

有界重复事件

start = '2012-06-15 05:00:00'
interval_unit = 'day'
interval_every = 1
stop = '2012-06-22 05:00:00'
type = 'positive'

从 2012 年 6 月 15 日开始,每天早上 5 点发生事件;最后一个活动是22日

无限重复事件

start = '2012-06-15 13:00:00'
interval_unit = 'week'
interval_every = 2
stop = null
type = 'positive'

从 2012 年 6 月 15 日开始,每两周下午 1 点举行一次活动

重复事件有例外

start = '2012-06-15 16:00:00'
interval_unit = 'week'
interval_every = 1
type = 'positive'
stop = null

start = '2012-06-22 16:00:00'
type = 'negative'
stop = '2012-06-22 16:00:00'

从 2012 年 6 月 22 日开始,每周都会在下午 4 点举行活动;但不是 22 日

于 2012-06-14T06:56:38.597 回答
0

最佳解决方案取决于您是希望支持标准合规性 (RFC5545) 还是仅在 MySQL 中工作。

取决于您的重复规则引擎需要的灵活性。如果您想要简单的规则(每月 1 日或每年 1 月,...),那么上面提供的解决方案已经详细说明。

但是,如果您希望您的应用程序提供与现有标准 (RFC5545) 的兼容性,这涉及更复杂的规则,您应该在构建日历应用程序时查看此 SO 帖子,我应该在我的数据库中存储日期或重复规则吗?

于 2012-12-26T19:59:43.077 回答
0

我实际上正在寻找与此类似的东西,到目前为止我的解决方案(在纸上我还没有开始构造或编码)存储在 2 个表中:

  1. “事件”将获得第一次出现的日期、标题和描述(加上自动增量 ID)。

  2. “events_recursion”表将交叉引用前一个表(例如带有 event_id 字段),并且可以以两种可能的方式工作:

    2.A:按日期存储所有事件(即每次出现一个条目,因此如果您想保存“本月的每个星期五”则为 4 或“2012 年每个月的第一天”为 12)

    2.B:或从一个字段中第一个事件的日期+另一个字段中最后一次发生(或递归结束)的日期开始保存间隔(我会以秒为单位保存),例如

ID:2 EVENT_ID:1 INTERVAL:604800(如果我没记错的话是一周)END:1356912000(应该是今年年底)

然后,当您打开显示时间表的 php 时,它会检查该月内仍然处于活动状态的事件,并在两个表之间进行连接。

我会使用 2 个交叉引用的表而不是将所有表保存在一个表中的原因只是因为我的项目会看到非常疯狂的事件,例如“每个星期五和每个月的第三个星期一”(在这种情况下是事件表中有 1 个条目,第二个表中有 2 个具有相同的“event_id”字段。顺便说一句,我的项目是为音乐教师准备的,他们在严格的时间表上进行了一些小工作,一次决定 3 或 6 个月,真是一团糟)。

但正如我所说,我还没有开始,所以我期待看到你的解决方案。

PS:请原谅(并忘记)我的英语,首先不是我的语言,其次是深夜,我困了

于 2012-06-17T00:11:42.877 回答
0

我建议围绕这一行提出一些建议:将您的事件表拆分为 2,因为显然有 2 种不同类型的重复事件和静态事件,并且根据类型,它们将具有不同的属性。

然后对于给定的事件查找,您将运行 2 个查询,针对每个事件类型一个。对于静态事件表,您肯定需要(至少)一个日期时间字段,因此对给定月份的查找只需在条件中使用该字段(其中 event_date > FirstDayOfTheMonth 和 event_date < LastDayOfTheMonth )。每周/每年视图的逻辑相同。

该结果集将与重复事件表中的第二个结果集相结合。可能的属性可能类似于 crontab 条目,使用星期几/每月几日作为 2 个主要变量。如果您正在查看月度视图,

select * from recurring_events where DayOfWeek in (1,2,3,4,5,6,7) or (DayOfMonth > 0 and DayOfMonth < @NumberOfDaysInThisMonth )

如果是每周/每年的视图,则再次类似。为了使接口更简单,使用存储过程和所有逻辑来确定“在日期 A 和日期 B 之间找到一周中的哪几天”。

拥有两个结果集后,您可以在客户端将它们聚合在一起,然后一起显示。这样做的好处是不需要“模拟/空记录”,也不需要预先填充的异步 cronjobs,查询很容易在运行中发生,如果性能确实下降,添加一个缓存层,特别是对于这样的系统自然缓存非常有意义。

于 2012-06-16T12:56:53.757 回答
0

我会按照我在这里解释的那样做。它将创建一个无限的压延机:

PHP/MySQL:对数据库中的重复事件进行建模,但查询日期范围

缺点是查询的时候会有一些计算。如果您需要一个高性能的网站,预加载数据将是您的最佳选择。您甚至不必预先加载日历中的所有事件,以便轻松更改单个事件中的值。但是从现在到......存储所有日期是明智的。

现在使用缓存值确实可以减少无限,但会提高速度。

遮阳篷的副本以方便访问:

我将创建一个只调用一个 col 的计数表,id并用 0 到 500 的数字填充该表。现在我们可以轻松地使用它来进行选择,而不是使用 while 循环。

Id
-------------------------------------
0
1
2
etc...

然后我将事件存储在一个表中Name as varcharstartdate as datetime并且repeats as int

Name    | StartDate            |   Repeats
-------------------------------------
Meeting | 2012-12-10 00:00:00  |   7
Lunch   | 2012-12-10 00:00:00  |   1

现在我们可以使用计数表来选择两个日期之间的所有日期:

SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
ORDER BY Id ASC


ShowDate
-------------------------------------
2012-12-09 00:00:00
2012-12-10 00:00:00
2012-12-11 00:00:00
2012-12-12 00:00:00
2012-12-13 00:00:00
2012-12-14 00:00:00
2012-12-15 00:00:00
2012-12-16 00:00:00
2012-12-17 00:00:00
2012-12-18 00:00:00
2012-12-19 00:00:00
2012-12-20 00:00:00

然后我们在事件表上加入它来计算开始日期和显示日期之间的差异。我们将结果除以repeats列,如果余数为0,则匹配。

所有组合变成:

SELECT E.Id, E.Name, E.StartDate, E.Repeats, A.ShowDate, DATEDIFF(E.StartDate, A.ShowDate) AS diff
FROM events AS E, (
    SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
    FROM `tally`
    WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
    ORDER BY Id ASC
) a
WHERE MOD(DATEDIFF(E.StartDate, A.ShowDate), E.Repeats)=0
AND A.ShowDate>=E.StartDate

这导致

Id  | Name       |StartDate             | Repeats   | ShowDate              | diff
---------------------------------------------------------------------------------
1   | Meeting    | 2012-12-10 00:00:00  | 7         | 2012-12-10 00:00:00   | 0
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-10 00:00:00   | 0
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-11 00:00:00   | -1
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-12 00:00:00   | -2
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-13 00:00:00   | -3
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-14 00:00:00   | -4
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-15 00:00:00   | -5
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-16 00:00:00   | -6
1   | Meeting    | 2012-12-10 00:00:00  | 7         | 2012-12-17 00:00:00   | -7
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-17 00:00:00   | -7
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-18 00:00:00   | -8
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-19 00:00:00   | -9
2   | Lunch      | 2012-12-10 00:00:00  | 1         | 2012-12-20 00:00:00   | -10

现在你可以(而且应该!)加快速度。例如,通过直接将日期存储在表中,您可以直接选择所有日期,而不是使用带有 dateadd 的计数表。您可以缓存并且不必再次计算的每一件事都是好的。

于 2012-12-27T11:35:34.483 回答