我可以批量更新每个日期时间吗?

Mar*_*cus 3 performance sql-server sql-server-2008-r2

当前数据库假定所有时间都是用户本地的。现在我们正在更改它,以便数据库以 UTC 存储所有内容,我们的应用程序将数据库中的 UTC 时间转换为每个用户的正确时区。

现在我们需要将数据库中的每个日期时间更新为 UTC 时间。有没有比让我们的应用程序(具有时区库)单独更新每个应用程序更好的方法?

由于夏令时 (BST),简单的 DATEADD 不是一种选择。大约有 1000 万个单元格需要更新。

Aar*_*and 5

假设您一次处理一个时区(例如英国时间),并且可以识别英国时间中的所有行(到目前为止可能是所有人),您可以创建一个日历表,其中包含 DST 时间更改日期夏令时。对于介于两者之间的所有时间,您减去一个小时,并且不要触摸那些没有的时间。样本数据:

CREATE TABLE dbo.SampleData
(
  LocalDT DATETIME,
  IsBritishTime BIT,
  UTC DATETIME
);

INSERT dbo.SampleData(LocalDT,IsBritishTime) VALUES
 ('20000101 00:00',1), -- should NOT be changed
 ('20000326 00:59',1), -- should NOT be changed
 ('20000326 01:01',1), -- should go back
 ('20001029 01:59',1), -- should go back
 ('20001029 02:01',1), -- should NOT be changed
 ('20001231 23:59',1), -- should NOT be changed
 ('20000401 00:00',0); -- should NOT be changed (different time zone)
Run Code Online (Sandbox Code Playgroud)

现在您可以使用一些相当棘手的 T-SQL 来识别范围。我们根据您的数据需要涵盖的年份从系统表中抓取一些行,然后我们计算每年 BST 的开始和结束时间。然后我们可以使用这些输出来更新主表:

SET DATEFIRST 7;

;WITH y AS 
(
  -- all the years from 2000 through 50 years after the current year:
  SELECT TOP (YEAR(GETDATE())-2000+51) 
    y = DATEADD(DAY,-1,DATEADD(YEAR,number,'20000101'))
      FROM [master].dbo.spt_values 
      WHERE [type] = N'P' ORDER BY number
),
s AS
(
  SELECT 
    -- BST starts last Sunday in March @ 1:00 AM UTC:
    BSTStart = DATEADD(HOUR, 1, DATEADD(DAY,(1-DATEPART(
      WEEKDAY,DATEADD(MONTH,3,y))) % 7,DATEADD(MONTH,3,y))),
    -- and ends last Sunday in October @ 2:00 AM UTC:
    BSTEnd = DATEADD(HOUR,2,DATEADD(DAY,(1-DATEPART(
      WEEKDAY,DATEADD(MONTH,10,y))) % 7,DATEADD(MONTH,10,y)))
  FROM y
)
-- UPDATE d SET 
SELECT *,
  UTC = DATEADD(HOUR, 
  CASE WHEN s.BSTStart IS NULL THEN 0 ELSE -1 END, d.LocalDT)
FROM dbo.SampleData AS d
LEFT OUTER JOIN s 
ON d.LocalDT >= s.BSTStart
AND d.LocalDT < s.BSTEnd
WHERE d.IsBritishTime = 1 
-- or more likely WHERE EXISTS 
-- (SELECT 1 FROM dbo.Users 
--    WHERE UserID = d.UserID
--    AND TimeZone = 'BritishTime');
Run Code Online (Sandbox Code Playgroud)

正如所写的那样,这只是向您显示连接并标识将使用不同的UTC 值更新的行(它们将具有非空的 BSTStart/BSTEnd 值)。如果您取消注释-- UPDATE d SET和注释SELECT *,,然后从 中选择数据dbo.SampleData,您应该会看到:

LocalDT          IsBritishTime UTC
---------------- ------------- ----------------
2000-01-01 00:00 1             2000-01-01 00:00
2000-03-26 00:59 1             2000-03-26 00:59
2000-03-26 01:01 1             2000-03-26 00:01 -- changed
2000-10-29 01:59 1             2000-10-29 00:59 -- changed
2000-10-29 02:01 1             2000-10-29 02:01
2000-12-31 23:59 1             2000-12-31 23:59
2000-04-01 00:00 0             NULL
Run Code Online (Sandbox Code Playgroud)

(实际上,当地时间 3 月 26 日 1:01 应该是不可能的 - 在凌晨 1:00,时钟向前滚动到凌晨 2 点,那么本地时间怎么可能是凌晨 1:01?所以,您可能想要那个小时的特殊情况并单独处理这些情况。这就是为什么我设计的每个系统从一开始就存储 UTC - 没有合理的理由来存储本地时间,因为我总是可以从 UTC 获取它,但我不能总是回来。)

当然,这将影响与该WHERE子句匹配的所有行,只是某些行不会具有不同的 值UTC。我强烈建议先把它放到一个不同的列中,而不是仅仅改变开始时间,这样你就可以维护原始数据,以防出现问题,你需要排除故障,你最终会需要这些数据,等等。你可以如果您想在现有查询中使用 UTC 值而不是一一更改这些值,请更改列名。

如果您在基础表上有支持索引,这将最有效。如果您针对多个时区执行此更新(并且有一列可以按本地时区标识行),那么为每个操作创建一个过滤索引可能是值得尝试的。如果您通过其他一些间接方式(例如 UserID)进行标识,也许您可​​以一次处理一个用户,或者来自每个时区的一组用户。分块在阻塞和日志影响方面效果最好,但没有关于结构和如何分区数据的其他细节,这是我能做的最好的。

我在上面提供了英国时间,因为它是问题中的示例,但是对于具有不同偏移量和不同开始/结束日期的不同时区,您显然需要不同的计算(这些可能每年都不同,例如在 2007 年)美国日期改变了)。

这是基于这样一个事实,即英国(特别是伦敦)在 3 月最后一个星期日当地时间凌晨 1 点(或 UTC)提前到凌晨 2 点 BST,并在 10 月最后一个星期日凌晨 2 点回到格林威治标准时间凌晨 1 点本地(UTC 时间凌晨 1 点)。在这里证实;链接礼貌@JackDouglas