如何在数据库中最好地表示“重复事件”?

46

我正在尝试使用C#开发一个基于调度器和日历的事件应用程序。其中一个关键需求是在数据库中表示循环事件。

最佳的数据库表示方式是什么?

更多细节:

在创建事件时,我还向某些用户发送邀请,并且被邀请者只能在指定的时间窗口(会议持续时间)内登录参加会议,或者当被邀请者试图在会议开始前5分钟登录时可以拒绝登录。


重复是如何进行的?每年的1月2日?每个星期一?每年3月的第二个星期四?还是以上所有情况都包括? - Svish
6个回答

43

在SQL Server中,sysjobssysjobsschedulesysschedules表非常适合这个作业。我不会重新发明轮子,只需复制它们的设计。

以下是来自sysschedules的一些重要字段:

freq_type

此计划运行工作的频率。

1 = 仅一次

4 = 每天

8 = 每周

16 = 每月

32 = 每月,相对于freq_interval

64 = SQL Server Agent服务启动时运行

128 = 计算机处于空闲状态时运行

freq_interval

执行作业的日期。取决于freq_type的值。默认值为0,表示未使用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上看到了一些相关代码。 - pixelfreak
@Bob,你能否包含一些选择查询,例如特定日期、周、月的事件是什么? - Saroj Shrestha
这个回答似乎误读了问题,它涉及到工作负载调度和周期性作业的 cron 风格执行计划,而不是问题中指定的重复人类事件和会议的日历安排。这两者的域模型、必要能力和结果截然不同。 - inopinatus

24

嗯,为了存储循环规则本身,你可以使用一个简化版本的RFC 5545(我建议你大幅简化它)。除此之外,这将使导出到其他应用程序变得很容易。

在做出这个决定后,对于数据库方面,您需要确定是否要存储每个事件发生,还是只需存储重复事件的一条记录,并在需要时扩展它。显然,当你已经扩展了所有内容时,查询数据库会更容易--但维护起来就更困难。

除非你喜欢编写一些可能难以测试的相当复杂的SQL语句(并且你需要针对各种角落案例有大量单元测试),否则我建议您使数据库本身相对“愚笨”,并在像Java或C#这样的语言中编写大部分业务逻辑--当然,这取决于您的数据库,它们都可能被嵌入存储过程中。

另一件您需要考虑的事情是,是否需要处理事件的异常情况 - 即一系列事件中的一个更改时间/位置等。

我有一些日历方面的经验(我花了大部分时间在通过ActiveSync开发Google Sync的日历模块上),我应该警告您,事情很快就会变得非常复杂。任何你认为不在范围内的事情都是一种福音。特别是,您是否需要在多个时区中工作?

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


2
RFC 5545于2009年9月取代了RFC 2445 - Simen Echholt
@JonSkeet,如果事件异常经常发生,您是否建议将每个事件“展开”? - Alexander Suraphel
3
这取决于你正在开发的应用程序类型 - 两种方法都有优缺点,但值得仔细考虑。 - Jon Skeet

7
这里被接受的答案太复杂了。例如,如果事件每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=每月
重复间隔
这是在重复之间的周期数。如果事件每5天重复一次,则此值为5,而重复为1。如果事件每2周重复一次,则此值为2,而重复为2。
重复日
如果用户选择了每月类型的重复,并且在月份的某一天(例如:10日或14日)发生。该值为该日期。如果用户未选择每月或特定日期的重复,则该值为0。否则,该值为1至31。
重复序数
如果用户选择了每月类型的重复,但是是日序数类型(例如:第一个星期一,第二个星期四,最后一个星期五)。这将具有该序数号码。如果用户未选择此类型的重复,则该值为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 一个问题,那么每月第一个和最后一个星期五重复发生的事件怎么办?应该使用什么组合? - Saroj Shrestha
1
@agapwlesu,您能否添加一个选择查询,以便查询一天、一周或一个月的数据? - Saroj Shrestha

0

我也一直在考虑这个问题,尽管还没有实现,但以下是我对一个简单解决方案的想法。

在设置重复事件时,让用户指定“结束日期”,并为每个日期创建单独的事件(基于重复选项)。由于这是一个重复事件,为每个事件设置一个唯一的“重复ID”。此ID将用于标记事件为重复事件,如果您更改未来的事件,可以提示用户通过删除并使用新的“重复ID”重新创建重复事件来将更改应用于其余未来事件,这也将区分此重复事件与先前已更改的事件。

希望这有意义,并希望得到任何评论。


我假设您询问的是存储在日历中的各个单独事件,而不是可以轻松研究/复制的各种重复规则类型? - Mark Redman

0

我会将重复事件记录为数据库中的两个不同事物。首先,在一个事件表中,记录每个事件的每次发生。其次,有一个重复事件表,其中记录您要求设置重复事件的详细信息。开始日期、周期性、发生次数等。

然后,您可以考虑通过将重复事件的PK作为FK放入每个事件记录中来将所有内容绑定在一起。但更好的设计是将事件表规范化为两个表,一个只是事件的基本信息,另一个包含详细信息,现在可以引用多个事件。这样,每个事件记录(无论是重复还是非重复)都具有对事件详细信息表的PK的FK。然后在事件详细信息中,记录重复事件的PK以及议程、被邀请者等。重复记录不驱动任何操作。例如,如果您想要列出所有重复事件的列表,则需要查找具有对重复事件的非空FK的所有事件详细信息。

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


-4

除此之外

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

“如果您希望,这将使其易于导出到其他应用程序。”

所述要求是否包括“必须轻松导出日历到其他应用程序”?我的印象是问题仅涉及构建第一个应用程序。

话虽如此,我的回答是:

您需要限制自己/用户可以支持的“重复性”类型。 如果您/用户想要最终获得可用的应用程序,“以上所有”或“没有限制”将不是有效的答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接