1

我正在创建一个在线日历应用程序(如 Google 日历或 MS Outlook),但不确定我应该如何存储数据以快速查询基本视图:每日、每周、每月。

在将其标记为重复之前,请记住我已经阅读了这里的几个主题,并且在大多数情况下,他们说“太难优化一般用途”。就我而言,这是一个非常具体的目的,我还没有看到有人问过它——一种非常具体的数据类型,许多开发人员[希望]都有过使用经验。

我需要快速获取任何出现在我的视图中的行(日、周、月),例如:

[end date of row] >= [start date of query]
AND
[start date of row] <= [end date of query]

我没有看到一个正常的 b-tree 索引可以很好地解决这个问题,但我也怀疑有人想出了一些可以与 SQL Server 2005(可能更旧)一起使用的聪明方法,因为日历应用程序一直存在,并且有100个。

我也对重复发生的事件以及如何存储这些事件感到好奇,尽管我目前的计划是始终读取所有这些事件(按“Is Recurring”索引)并在代码中优化它,而不是 SQL。与随着时间的推移可能会变得非常大的正常事件不同,这些事件不应该有很多。

更新:这个问题也是独一无二的,因为它是一个日历应用程序,我需要存储带有时区信息的日期,但我的查询不能是特定于时区的。如果您有使用日历应用程序的经验,您就会明白我的意思(如果没有,您只会说存储为 UTC)。

4

2 回答 2

1

不久前,我不得不处理与此类似的事情。它是急诊室软件,我们必须进行大量的日期范围和班次计算,并且不能让时区问题影响我们。我们最终要做的是为每个日期存储 6 列。一组三列(日期,日期为 int,时间为 int)一次作为输入,一次作为 UTC。所有计算均使用 UTC 完成,以避免时区问题。如果需要,您还可以添加时区列。

Date as datetime -- in the time zone entered - Used for display
UDate as a datetime -- The UTC version of the date.
            -- Used for display and some calculations
IntDate as int -- Date as an int YYYYMMDD so 20130417
IntUDate as int -- UTC date as an int.   
IntTime as int -- Time as an int HHMMSS.  
              -- So for 1:12:40 PM it would be 131240 and for 1:12:40 AM 
              -- it would be 11240.  Note only 5 places.
              -- May need to be decimal if you need more precision)
IntUTime as int -- Sames as IntTime but for the UTC datetime

您可能不需要时间列。我们这样做是因为班次计算。根据需要在列上创建索引。至少 IntDate 和 IntUDate 列。因为这些是整数,所以索引会非常快。请注意,所有计算都应使用 UTC 列来完成,以避免时区问题。显示通常使用日期列完成。

接下来创建一个日期表。这里你必须意识到,这张表相当窄,你可以填写数百年的日期,但仍然没有那么大的表。每 100 年大约 36525 行。添加索引,它再次非常快。

我们的看起来像这样。

CREATE TABLE DateTable (
    [Date] Int PRIMARY KEY,
    [DayOfYear] smallint,
    [Month] tinyint,
    [Quarter] tinyint,
    [Year] smallint,
    [LeapYear] bit,
    [DaylightSavings] bit
    )

在(Year,DayOfYear),(Year,Month,Day)等上有索引。无论您需要什么。您也可以添加您需要的任何其他列。比如说闰年、假期、每月的第一天、每月的最后一天等。

如果您需要说出给定年份/季度的所有内容,您可以在日期表上添加一个联接,并且所有内容都被很好地编入索引。

使用上面的示例,您可以执行以下操作:

SELECT *
FROM MyTable
WHERE EXISTS
    (SELECT 1 FROM DateTable
    WHERE DateTable.[Date] BETWEEN MyTable.UTCStartDate AND MyTable.UTCEndDate
    AND DateTable.[Date] BETWEEN @StartDate AND @EndDate)
于 2013-04-17T21:50:18.333 回答
1

因为我认为时区问题对日历应用程序很重要,并且一些现有的应用程序不能很好地处理这个问题(甚至是 Outlook,2007 年之前),所以我将此信息添加为答案,并作为先前评论的后续行动。

我希望 Google 开发人员也能阅读这篇文章,因为基于http://support.google.com/calendar/answer/2367918?hl=en,他们似乎也有“转移”问题。这是他们所说的,这对我来说似乎是错误的/不可接受的:

但是,如果一个国家/地区决定在切换到 DST 甚至整个时区时进行更改,则此过程并不总是有效。如果您在我们知道更改之前创建了活动,日历会使用创建时可用的信息将您的时区转换为 UTC。知道时区更改后,日历将使用新规则在您的时区显示事件,这可能会导致事件在您的日历中发生变化

粗体字的最后一部分是不应该发生的事情。如果我将会议设置为太平洋标准时间上午 8 点,它将在太平洋标准时间上午 8 点举行,它不会仅仅因为某些时区规则发生变化而“转移”。

在日历应用程序中,如果用户输入“2020 年 4 月 26 日下午 12:00,亚利桑那时间”的事件。如果您将其转换为 UTC 进行存储,就像大多数应用程序一样,您将把它保存为(使用我输入 ths 时的规则)“2020 年 4 月 26 日,下午 7:00,UTC”。

然后,如果您想查询“2020 年 4 月 26 日下午 12:00,亚利桑那时间”是否有任何事件发生,您将查询“2020 年 4 月 26 日,下午 7:00,UTC”,因为那是当前的转换规则告诉你要做的。

起初你会找到这个项目,正确的,是的。

现在,如果时区规则发生变化,比如说在 2018 年,亚利桑那州变为 -0800 UTC 而不是 -0700 UTC(也许他们决定支持 DST,谁知道呢)。然后您再次进行查询,查找在“2020 年 4 月 26 日下午 12:00,亚利桑那时间”发生的任何事件。这一次,当您进行查询时,您将寻找“2020 年 4 月 26 日,晚上 8:00,UTC”。这是因为您在进行查询时只知道使用当前规则,而您不知道某些数据在保存时使用了旧规则。 因此,即使您应该找到该项目,您也找不到该项目,并且用户错过了该事件。

现在,您决定显示该项目的方式因应用程序而异,但对于日历/日程安排应用程序,它永远不应更改用户输入的时间。当用户查看它时,它仍应显示为“2020 年 4 月 26 日,下午 12:00,亚利桑那时间”。但是,由于规则更改,您用于查询的 UTC 值与该时间不匹配。

一个好的日历应用程序应该处理这个问题的方式(从我经过大量研究后了解到的)是:

  1. 存储用户每次输入的以下信息:

时区(在 Windows 中,我使用 Windows 时区 ID,但这可以来自其他来源,只要它是唯一的并且是您用来进行转换的)。

用户输入的日期和时间

使用用户输入信息时的规则将日期和时间转换为 UTC(问题区域)

  1. 每当时区规则发生变化时,请确保您的转换代码已更新(即 Windows 更新、库更新等),并且在同一更新过程中,使用新规则更新数据库中的所有 UTC 时间。

“更新”过程是这样的:

  1. 查询更改时区的所有记录。如果需要,可以过滤在规则更改之后具有日期的记录,因为之前的任何记录都没有更改(这将基于用户输入的日期,而不是 UTC 值)。

  2. 对于这些记录中的每一个(如果您没有精确过滤,或者即使您只是对每个时区的数据库中的每条记录进行盲目操作,也没有关系)...运行您在记录时执行的相同转换代码最后添加/编辑,只需获取用户输入的值并使用当前规则将其转换为 UTC,然后保存新的 UTC 值。

需要这种混乱的证据是结果将是您的某些 UTC 值更改,并且用户输入的值都没有更改(因为我们不能允许这样做,这对于日历应用程序来说是愚蠢的,除非事件时间基于 UTC,在这种情况下,用户应该在添加时区时将时区设置为 UTC)。

想想如果您不执行此更新过程会发生什么。您基于 UTC 所做的所有查询都不正确。他们怎么可能不是?

于 2013-04-18T21:16:45.123 回答