【问题标题】:SQL Infinite Calendar PatternSQL 无限日历模式
【发布时间】:2012-06-13 22:03:40
【问题描述】:

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

方法#1

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

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

方法#2

通过子查询和JOIN 使用静态日历创建和连接给定模式。

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

方法#3

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

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

方法 #4(由 Veger 建议)

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

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


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

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

【问题讨论】:

  • #3 是大多数日历系统所做的,但它们有一个配置项,可让您设置“展望”未来多远。这样,在您定义的时间段内它会很快;但之后一段时间的数据较慢且较少。不是这个或那个答案,而是一个这个和那个答案。
  • 是的,这是一个相当不错的解决方案,我会考虑。我无法放弃“这不是真正的模式”的感觉,但如果需要的话,我可能会忽略这种感觉 :)
  • 第 4 种方法如何,您分别获取模式重复(甚至可能来自另一个表)并使用此信息为请求的时间段添加事件?这仅存储模式而不是所有事件,可能更节省资源..?虽然我不确定……
  • 谢谢,我已将您的方法添加到问题中,希望我能完全理解。
  • 您是否还打算在该日历中存储一次事件?

标签: php mysql codeigniter calendar


【解决方案1】:

我以前做过这种日历。我发现最好的方法是按照计划 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).'"

我还没有测试过这些查询,所以可能有一些搞砸的括号之类的东西。但这只是一般的想法。

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

【讨论】:

  • 谢谢。这是我正在寻找的答案的一半。我个人认为保存start_unixintervalformatamountend_unix (can be NULL) 会更容易。格式 1 分钟为 60,格式 1 小时为 3600。然后您只需运行 (start_unix * (interval * format * i)) 即可获得每次出现的 unix。无论如何,我对如何获取这些数据更感兴趣。最好我希望将所有内容都收集在同一张表中以进行所有提取等。但我担心创建临时表是否每个页面加载可能会滞后。
【解决方案2】:

我最喜欢 Veger 的解决方案 .. 而不是填充多行,您可以只填充模式。我建议使用crontab 格式.. 反正效果很好。

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

【讨论】:

  • 是否会实现这一点,以便我可以将它与 MySQL 一起使用,或者我是否必须将它与获取的一次性事件查询一起计算?
  • 您的所有事件都将使用此系统(您也可以以这种格式存储一次性事件)。该模式存储在数据库中,而不是事件日期本身,但该模式可能适用于单个日期。
【解决方案3】:

自从我仍在使用 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 日

【讨论】:

  • 如果我理解正确,您的意思是我应该将模式和单个事件保存在同一个表中并提取startstop 之间的所有出现?当实际时间表可能发生时,我会在 php 中计算吗?最好,但也许我有点被宠坏了,我想通过简单的 MySQL 将它们作为“正常”事件获取,但也许这只是一个不会实现的梦想?
  • @RobinCastlin SQL 将帮助您缩小事件范围,因此如果您的大多数事件都是“单一的”,那么额外的 php 代码可以忽略不计。
  • 我计划以更多方式处理这些事件,而不是回显一个简单的日历。例如,我也想将它们显示在一个列表中。我计划用大约 5 到 10 个图案填充一周的 70%。这使得 php 计算和过滤之后看起来很全面。
  • @RobinCastlin 好的,但是您期望什么样的数据大小?这些东西也应该是可缓存的。
  • 数据量不会太大。你会如何建议缓存?
【解决方案4】:

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

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

此结果集将与重复事件表中的第二个结果集组合。可能的属性可能类似于 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,查询很容易即时发生,如果性能实际上下降,添加一个缓存层,尤其是对于这种性质的系统来说,缓存非常有意义。

【讨论】:

    【解决方案5】:

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

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

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

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

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

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

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

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

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

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

    【讨论】:

      【解决方案6】:

      【讨论】:

      • 多么方便的解决方案啊,如果我愿意的话可以说是提前一年给每个静态事件。这也将有助于通过 ID 和类似方式引用每个事件。我肯定会考虑这种方法。
      【解决方案7】:

      最佳解决方案取决于您是希望符合标准 (RFC5545) 还是只在 MySQL 中工作。

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

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

      【讨论】:

        【解决方案8】:

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

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

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

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

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

        我将创建一个只包含一个名为 id 的列的计数表,并用从 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
        

        然后我们将其加入事件表中以计算开始日期和演出日期之间的差异。我们将结果除以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-10-24
          • 2022-01-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多