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

Posted

技术标签:

【中文标题】在数据库中表示“重复事件”的最佳方式是啥?【英文标题】:What is the best way to represent "Recurring Events" in database?在数据库中表示“重复事件”的最佳方式是什么? 【发布时间】:2010-12-07 11:16:02 【问题描述】:

我正在尝试在 C# 中开发一个依赖于调度程序和日历的事件应用程序,其关键要求是在数据库中表示重复发生的事件。 在数据库中表示重复事件的最佳方式是什么?

更多详情:

在创建活动时,我还会向某些用户发送邀请,并且应允许受邀者仅在指定的窗口(会议持续时间)内登录会议,或者在受邀者尝试登录时拒绝登录说,在预定的会议开始前 5 分钟。

【问题讨论】:

如何反复出现?每年1月2日?每逢星期一?三月的每个第二个星期四?以上都是? 【参考方案1】:

SQL Server 中的sysjobs、sysjobsschedule 和sysschedules 表在这方面做得很好。我不会重新发明***,我只会复制他们的设计。

这里是来自sysschedules的一些重要字段

freq_type

作业为此计划运行的频率。

1 = 仅一次

4 = 每天

8 = 每周

16 = 每月

32 = 每月,相对于 freq_interval

64 = 在 SQL Server 代理服务启动时运行

128 = 在计算机空闲时运行

freq_interval

作业执行的天数。取决于freq_type 的值。默认值为 0,表示未使用 freq_interval。 freq_type 的值对 freq_interval 的影响

1(一次)freq_interval 未使用(0)

4(每天)每 freq_interval 天

8(每周)freq_interval 是以下一项或多项:1 = 星期日 2 = 星期一 4 = 星期二 8 = 星期三 16 = 星期四 32 = 星期五 64 = 星期六

16(每月)在每月的 freq_interval 天

32(每月,相对)freq_interval 是以下之一:1 = 星期日 2 = 星期一 3 = 星期二 4 = 星期三 5 = 星期四 6 = 星期五 7 = 星期六 8 = 第 9 天 = 工作日 10 = 周末

64(在 SQL Server 代理服务启动时启动)freq_interval 未使用 (0)

128(在计算机空闲时运行)freq_interval 未使用(0)

freq_subday_type

freq_subday_interval 的单位。可以是以下值之一: 值说明(单位)

1 在指定时间

2 秒

4 分钟

8 小时

freq_subday_interval

每次执行作业之间发生的 freq_subday_type 周期数。

freq_relative_interval

当freq_interval 出现在每个月时,如果freq_interval 为32(每月相对)。可以是以下值之一:

0 = freq_relative_interval 未使用

1 = 第一个

2 = 秒

4 = 第三

8 = 第四

16 = 最后

freq_recurrence_factor

计划执行作业之间的周数或月数。仅当 freq_type 为 8、16 或 32 时才使用 freq_recurrence_factor。如果此列包含 0,则未使用 freq_recurrence_factor。

【讨论】:

很好奇,我们可以用 cron 样式的字符串替换其中的一些字段吗?唯一的缺点是你必须解析它,但它不应该太难,我在 SO 中看到了一些代码。 @bob 你能否包含一些选择查询,例如特定日期、星期、月份的事件是什么 这个答案似乎误读了这个问题,它与工作负载调度和定期作业的 cron 样式执行计划有关,而不是问题中指定的重复人类事件和会议的日历。两者的领域模型、必要能力和结果截然不同。【参考方案2】:

好吧,要存储重复规则本身,您可以使用RFC 5545 的缩减版本(我真的建议您大幅缩减)。除此之外,如果您愿意,这将使导出到其他应用程序变得容易。

在您做出决定后,对于数据库端,您需要确定是要存储每次发生的事件,还是只存储重复事件的一条记录,将其扩展为以及何时需要。显然,当您已经扩展所有内容时,查询数据库要容易得多 - 但它会使维护变得更加棘手。

除非您想编写一些可能难以测试的非常复杂的 SQL(并且您需要 大量 对各种极端情况进行单元测试),否则我建议您创建数据库它本身相对“笨拙”,并用 Java 或 C# 等语言编写大部分业务逻辑 - 当然,根据您的数据库,这两种语言都可以嵌入到存储过程中。

您需要问自己的另一件事是您是否需要处理事件的例外情况 - 一系列事件中的一个事件更改时间/地点等。

我有一些日历方面的经验(我去年的大部分时间都在通过 ActiveSync 处理 Google Sync 的日历位),我应该警告您,事情很快就会变得复杂真的。任何你认为“超出范围”的东西都是一种祝福。特别是,您需要在多个时区工作吗?

哦,最后 - 在使用日历运算进行实际算术时要非常非常小心。如果您打算使用 Java,使用 Joda Time 而不是内置的 Calendar/Date 类。他们会帮助你很多。

【讨论】:

RFC 5545 在 2009 年 9 月取代了 RFC 2445。 @JonSkeet 如果事件异常经常发生,您是否建议将每个事件都“扩展”? @AlexanderSuraphel:这完全取决于您正在构建的应用程序 - 两种方式都有利有弊,但至少值得仔细考虑。【参考方案3】:

这里接受的答案太复杂了。例如,如果事件每 5 天发生一次,则 5 存储在 freq_interval 中,但如果每 5 周发生一次,则 5 存储在 freq_recurrence 中。最大的问题是 freq_interval 意味着三个不同的东西,具体取决于 freq_type 的值(每日重复出现之间的天数,每月重复出现的日期,或每周或每月相对的星期几)。此外,当 1,2,4,8... 类型序列不必要且无用时,使用它。例如,freq_relative_interval 只能是“其中之一” 可能的值。这与下拉框或单选按钮类型输入一致,而不是可以选择多个选项的复选框类型输入。对于编码和人类可读性,这个序列会妨碍并且仅使用 1,2,3,4... 更简单,更有效,更合适。最后,大多数日历应用程序不需要次日间隔(事件在一天内发生多次 - 每隔这么多秒、几分钟或几小时)。 但是,话虽如此,这个答案确实帮助我完善了我如何做到这一点的想法。在将它与其他文章混合并匹配之后,从我在 Outlook 日历界面和其他一些来源中看到的内容来看,我想出了这个:

重复 0=不重复 1=每天 2=每周 3=每月

recurs_interval 这是重复之间的周期数。如果事件每 5 天重复一次,这将有 5,recurs 将有 1。如果事件每 2 周重复一次,这将有 2,recurs 将有2.

recurs_day 如果用户选择每月类型重复,则在每月的给定日期(例如:10 日或 14 日)。这有那个日期。如果用户没有选择每月或每月的特定日期重复,则该值为 0。否则值为 1 到 31。

recurs_ordinal 如果用户选择了每月类型的重复,但选择了序数类型的日期(例如:第一个星期一、第二个星期四、上个星期五)。这将具有该序数。如果用户没有选择这种重复类型,则值为 0。 1=第一个 2=秒 3=第三 4=第四 5=最后一个

recurs_weekdays 对于每周和每月顺序重复,这将存储重复发生的工作日。 1=星期日 2=星期一 4=星期二 8=星期三 16=星期四 32=星期五 64=星期六

所以,例子: 因此,每 4 周的周六和周日将是

recurs = 2 ==> 每周重复 recurs_interval = 4 ==> 每 4 周 recurs_weekdays = 65 ==>(星期六=64 + 星期日=1) recurs_day 和 recurs_ordinal = 0 ==> 未使用

同样,每 6 个月的第一个星期五是

recurs = 3 ==> 每月重复 recurs_interval = 6 ==> 每 6 个月 recurs_ordinal = 1 ==> 在第一次出现时 recurs_weekdays = 32 ==> 周五

根据另一个字段的值,拥有一个表示三种完全不同的事物的字段与此无关。

在用户界面方面,我让用户指定日期、开始时间和结束时间。然后,他们可以指定是否需要除无之外的重复类型。如果是这样,该应用程序会扩展网页的相关部分,为用户提供上述内容所需的选项,看起来很像 Outlook 选项,除了在每日重复下没有“每个工作日”(这与每个星期一至星期五每周重复一次),并且没有每年重复一次。如果有重复,那么我还要求用户指定一个在今天一年内的结束日期(用户希望这样,并且它简化了我的代码) - 我不做无休止的重复或“在 # 之后结束# 次。”

我将这些字段与用户选择一起存储在我的事件表中,并将其展开到包含所有事件的时间表表中。这有助于碰撞检测(我实际上是在做一个设施预订应用程序)和编辑个别事件或重构未来事件。

我的用户都在 CST,为此我感谢上帝。现在这是一个有用的简化,如果将来用户群要扩大到这个范围之外,那么我可以弄清楚如何处理它,作为一个很好的分离任务。

更新 自从我第一次写这篇文章以来,我确实在“每个工作日”中添加了每日发生次数。我们的用户很难想到您可以将每周重复用于从一周的星期四到下周的星期二发生的事件,并且仅在工作日发生。即使他们已经有另一种方法可以做到这一点,对他们来说拥有这个更直观。

【讨论】:

@agapwlesu 一个问题,这个月的第一个和最后一个星期五再次发生的事件怎么样。会是什么组合 @agapwlesu 你能添加一天、一周或一个月的选择查询吗【参考方案4】:

我也一直在考虑这个,虽然没有实现,但这些是我对一个简单解决方案的想法。

在设置周期性事件时,让用户指定“结束日期”并为每个事件创建单独的事件(基于周期性选项)。因为它是一个重复事件,所以为每个事件设置一个唯一的“重复 ID”。然后,此 ID 将用于将事件标记为重复发生,如果您更改未来事件,您可以通过删除并重新创建具有新“重复 ID”的重复事件来提示用户将其应用于其余未来事件。还将将此重复事件与之前已更改的事件区分开来。

希望这是有道理的,并且会喜欢任何 cmets。

【讨论】:

我假设您询问的是为日历存储的各个事件,而不是可以轻松研究/复制的各种类型的重复规则?【参考方案5】:

我会将重复事件记录为数据库中的两个独立事物。首先,在事件表中,记录事件的每一次发生。其次,有重复表,您可以在其中记录设置重复事件所需的详细信息。开始日期、周期、出现次数等。

然后,您可能会考虑通过将重复的 PK 作为 FK 放入每个事件记录中来将它们捆绑在一起。但更好的设计是将事件表规范化为两个表,一个只是事件的准系统,另一个包含详细信息,现在可以引用多个事件。这样,每个事件记录,无论是否重复,都有一个指向 eventdetails 表的 PK 的 FK。然后在 eventdetails 中,在某处记录重复的 PK 以及议程、受邀者等。重复记录不会驱动任何事情。例如,如果您想要一个所有重复事件的列表,您可以查看所有事件的详细信息,以查找具有非空 FK 的所有事件。

您需要小心同步所有这些内容,以便在重复数据发生更改时插入或删除事件。

【讨论】:

【参考方案6】:

“除此之外”

这是否包括“非常要求”?

“如果您愿意,可以轻松导出到其他应用程序。”

所述要求是否包括“必须易于将日历导出到其​​他应用程序”?我的印象是,问题仅在于构建 FIRST 应用程序。

也就是说,我自己的回应:

您需要限制您自己/您的用户对您的系统能够支持的“重复”类型进行限制。如果您/您的用户希望最终获得可用的应用程序,“以上所有”或“无限制”将不是有效答案。

【讨论】:

以上是关于在数据库中表示“重复事件”的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 Scheme (R6RS) 中表示代数数据类型构造函数的惯用方式是啥?

在固定长度向量中表示文档集合的最佳方法是啥?

如何在数据库中表示银行账户信息?

在c#[close]中表示路由(一个地方到下一个地方等等)的最佳方式是什么?

在 CocoaTouch (UIKit) 中表示线性过程的正确方法是啥?

在 Apiary 中表示输入参数是日期/时间的正确格式是啥?