SQL无限日历模式

时间:2012-06-10 21:58:48

标签: php mysql codeigniter calendar

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

方法#1

创建一个接受参数fromto的函数。此函数将创建一个临时表表,该表通过INSERT ... SELECT导入现有的静态计划。之后,它会读取模式表,并根据fromto通过peroid填充临时表。

这个解决方案看起来很不错,因为查询将更简单地获取数据并且它可以工作到无穷大,因为您可以根据您加载的月份重新填充表格。我很好奇的是,这可能是一种迟钝的方式。

方法#2

通过子查询创建并加入给定的模式,并使用静态日历创建JOIN

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

方法#3

基本上只是INSERT模式,比如提前一年。然后我猜一个cron工作会重新制作,以便提前一年。

这是一种简单的方法,但感觉就像存储了大量不需要的数据,并没有真正给出我所追求的无限。

方法#4(由Veger建议)

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

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


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

就个人而言,我最喜欢方法#1,但我很好奇是否每次通话重新填充日历表都很迟钝。

8 个答案:

答案 0 :(得分:14)

我之前已经建立了这种日历。我发现最好的办法就是按照计划的方式来接近它。因此,在数据库中,为分钟,小时,星期几,月和星期几创建一个字段。

对于6月和8月的每个星期五晚上10点的活动,您的参赛作品将是

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).'"

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

因此,您可以为您正在显示的每一天运行一个选择,或者您可以选择返回视图的所有内容(月,周等)并循环播放每天的事件。

答案 1 :(得分:9)

我最喜欢Veger的解决方案..而不是填充多行,你可以填充模式。我建议使用crontab格式..无论如何它都运行良好。

您可以在加载日历时查询给定客户的所有模式,并根据模式填写事件。除非你有一个单一用户的数千种模式,否则这应该不会那么慢。它也应该比长时间存储大量行事件更快。您必须一次选择所有模式并进行一些预处理,但再一次,您希望每个用户有多少个模式?即使1000左右也应该非常快。

答案 2 :(得分: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日)
  3. 帮助者查询

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

    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-06-15上午9点

    有界重复事件

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

    事件发生在每天凌晨5点,从2012-06-15开始;最后一次活动是在22日

    无限重复事件

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

    事件每两周一次,从2012-06-15开始

    重复事件

    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'
    

    事件发生在每周下午4点,从2012-06-22开始;但不是在22日

答案 3 :(得分:0)

我会建议一些与此相关的内容: 将您的事件表拆分为2,因为显然有2种不同类型的重复事件和静态事件,具体取决于它们具有不同属性的类型。

然后,对于给定的事件查找,您将运行2个查询,每个查询对应一个事件类型。对于静态事件表,您肯定需要(至少)一个日期时间字段,因此对于给定月份的查找将仅在条件中使用该字段(其中event_date&gt; FirstDayOfTheMonth和event_date&lt; 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,查询可以轻松实现,如果性能实际上降级,添加缓存层特别是对于这种性质的系统,缓存非常有意义。

答案 4 :(得分:0)

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

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

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

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

    2.B:或者从字段中第一个事件的日期+在另一个字段(例如

  3. ID:2 EVENT_ID:1 INTERVAL:604800(如果我没有记错的话,一周) 结束:1356912000(应该是今年年底)

    然后当你打开显示时间表的php时,它将检查那个月中仍然活动的事件,并在两个表之间建立联合。

    我之所以使用2个表交叉引用而不是将所有表保存在一个表中的原因只是来自我的项目看到非常疯狂的事件的事实,例如“每个星期五和每个月的第3个星期一”(在此案例将是事件表中的1个条目和2个在第二个表中具有相同“event_id”字段的条目。顺便说一下,我的项目是针对音乐教师的,这里有一个严格的时间表上的小工作,一次决定3或6个月,真是一团糟)。

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

    PS:请原谅(并忘记)我的英语,首先不是我的语言,第二个是深夜,我很困

答案 5 :(得分:0)

答案 6 :(得分:0)

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

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

但是,您是否希望您的应用程序提供与现有标准(RFC5545)的兼容性,这涉及更复杂的规则,您应该查看此SO帖子when building a calendar app, should i store dates or recurrence rules in my database?

答案 7 :(得分:0)

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

PHP/MySQL: Model repeating events in a database but query for date ranges

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

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

awnser的副本以便于访问:

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

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

然后我会将事件存储在包含Name as varcharstartdate as datetimerepeats 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

然后我们在事件表上加入它来计算startdate和showdate之间的差异。我们在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的计数表。你可以缓存并且不必再次计算的所有东西都很好。