【问题标题】:How to store calendar app data in SQL Server 2005如何在 SQL Server 2005 中存储日历应用程序数据
【发布时间】:2013-04-17 20:11:06
【问题描述】:

我正在创建一个在线日历应用程序(如 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)。

【问题讨论】:

  • 感谢马克的编辑。我仍然无法克服特定于 Microsoft 的“SQL Server”,但我想这是行业标准,所以我会尽量记住从现在开始删除 MS/Microsoft 部分。

标签: sql-server sql-server-2005 calendar indexing


【解决方案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)

【讨论】:

  • 谢谢,这给了我很多思考。一旦我有时间,我会回到这个。我认为这仍然受到此答案顶部stackoverflow.com/a/1947792/1042232 中解释的基本 b-tree 问题的影响,就查询速度而言,但在 datetime 上使用 int 确实可能会使其更快,而且您的加入想法确实使它范围更容易。你有关于大型数据集的任何个人资料信息吗?如果没有,我会进行一些测试,只是好奇。
  • 希望您已经知道如何处理时区转换。例如,如果日期的时区规则(存储在用户时区中的规则)发生变化,那么您可能需要更新 UDate,因为它不再是相同的 UTC 时间。也就是说,除非您希望“太平洋标准时间上午 8 点”在规则更改时“转移”,以便它仍然是相同的 UTC 时间(但现在不再是“太平洋标准时间上午 8 点”),否则可能不是您的最终用户想要的。每当规则发生变化时,必须保持 UDates 更新(大规模数据库更新)似乎很痛苦,但我想可能不是更好的处理方法。
  • UTC 不会改变,至少目前不会改变,也不会基于时区。基本上 UTC 完全忽略时区。它与 GMT 密切相关。这是 wiki 定义:en.wikipedia.org/wiki/Coordinated_Universal_Time。我们使用 GetUTCDate() 来加载列,因此我们不必担心系统上的时区。使用 UTC,无论时区规则更改或夏令时等,您都可以进行计算。这就是它的目的。
  • 正确,但在您的设计中,我认为您是在说这个(例如)。 “太平洋标准时间 2015 年 4 月 18 日上午 8 点” = 一些 UTC 时间。如果时区规则发生变化,该计算可能会随着时间而变化。如果您只关心美国,则不会经常发生,但在某些国家/地区,它们每年都会发生变化。无论如何,现在接受这个答案,因为在分析之后它会足够快以满足我的需求,并且很好地解决了主要要求。我认为这个时区转换问题已经包含在其他 SO 答案中。
  • 我很高兴它有所帮助。不过,我认为您正在向后看整个 UTC。 UTC 不变。时区日期可能而且应该被视为“仅显示”。如果时区计算发生变化,则时区版本可能与输入时的版本不同。 UTC 版本应该仍然相同。那时,当您显示时区日期时,您有两个选择。显示最初输入的日期/时间,或根据 UTC 计算。
【解决方案2】:

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

我希望谷歌开发者也能读到这篇文章,因为基于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 所做的所有查询都不正确。他们怎么可能不呢?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-09
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 1970-01-01
    • 2010-09-16
    相关资源
    最近更新 更多