将DST合并到SQL脚本中

时间:2014-06-28 19:59:04

标签: sql sql-server sql-server-2008 datetime dst

我原来的问题太紧凑而且到处都是,所以我试着在这里清理它。试图弄清楚如何在美国的SQL脚本中正确实现DST。

a.ActualEnd = DateTime value& c.TimeZoneBias =根据用户本地时间偏移的分钟数

更新:下面的整个脚本

INSERT INTO [AS400].S062f7ar.APLUS83MDS.PEPAPPTS01  
(PPCONO, PPREP1, PPDATE, PPCOUNT)
select '1' as PPCONO, 
       b.new_SalesrepId as PPREP1, 
       MAX(CONVERT(varchar(8), 
       (a.ActualEnd - c.TimeZoneBias / cast(24 * 60 as float)), 112)) as PPDATE,       
       count(b.new_SalesrepId) as PPCOUNT 
from ActivityPointerBase as a
  join SystemUserExtensionBase as b 
   on b.SystemUserId = a.OwnerId
  join UserSettingsBase as c 
   on c.SystemUserId = b.SystemUserId
where b.new_SalesrepId <> '99999999' 
 and a.ActivityTypeCode = '4201' 
 and b.new_SalesrepId is not NULL 
 and a.StateCode = '1' 

 and CONVERT(varchar(8), 
             a.ActualEnd - c.TimeZoneBias / cast(24 * 60 as float), 
             112) >= dateadd(day,datediff(day,
                                            1,
                                          CONVERT(varchar(8), 
                                                  GetDate(), 
                                                  112)
                                         ),
                             0) 
 and CONVERT(varchar(8), 
             a.ActualEnd - c.TimeZoneBias / cast(24 * 60 as float), 
             112) < dateadd(day,datediff(day,
                                           0,
                                         CONVERT(varchar(8), 
                                                 GetDate(), 
                                                 112)
                                         ),
                             0)
 group by b.new_SalesrepId, 
   CONVERT(varchar(8), 
   (a.ActualEnd - c.TimeZoneBias / cast(24 * 60 as float)), 112)
 order by b.new_SalesrepId ASC;

希望将美国的DST正确纳入此声明。我不希望每次DST到处时都必须手动更改脚本。

1 个答案:

答案 0 :(得分:1)

假设

由于这些要点未在问题中明确定义,我在此陈述我对它们的假设。

  1. 当前服务器存储数据库中的所有日期和时间均为UTC。
  2. 查询是以用户当地时间返回所有日期和时间。
  3. 查询是返回日期范围内的所有记录,但是要根据每个用户的当地时间。
  4. TimeZoneBias是美国化的偏差,以分钟为单位,其值为UTC-(420/60)或UTC-7(MST),-120的值为UTC - ( - 120/60)或UTC + 2(EET)。
  5. 解决方案

    我看到三种可能的解决方案。我相信所有这些都是可行的,假设首先在其中几个上执行一些数据迁移。

    解决方案1:DATETIMEOFFSET

    在表ActivityPointerBase中将ActualEnd列实现为DATETIMEOFFSET类型。应用程序将需要提供用户当前本地偏移量的时间戳。然后简单地使用CONVERT(DATE,a.ActualEnd)来获取用户的本地日期。甚至允许您比较用户之间的时间戳,因为数据库可以在比较两个DATETIMEOFFSET值时隐式执行偏移计算。还有一些函数可以将DATETIMEOFFSET转换为UTC DATETIME,以便与其他表进行比较。

    SELECT '1' as PPCONO,
           b.new_SalesrepId AS PPREP1,
           CONVERT(VARCHAR(8), a.ActualEnd, 112) AS PPDATE,
           COUNT(b.new_SalesrepId) AS PPCOUNT
    FROM ActivityPointerBase AS a
    JOIN SystemUserExtensionBase AS b ON b.SystemUserId = a.OwnerId
    WHERE b.new_SalesrepId <> '99999999' 
    AND a.ActivityTypeCode = '4201' 
    AND b.new_SalesrepId IS NOT NULL 
    AND a.StateCode = '1' 
    AND CONVERT(DATE, a.ActualEnd) = CONVERT(DATE, DATEADD(DAY, -1, GETDATE()))
    GROUP BY b.new_SalesrepId,
             CONVERT(VARCHAR(8), a.ActualEnd, 112)
    ORDER BY b.new_SalesrepId ASC;
    

    解决方案2:存储您需要的数据

    将用户本地时间和UTC时间存储在数据库中。由于您以后显然需要用户本地时间,因此请将其存储为系统的要求。然后,您在ActivityPointerBase表中同时拥有ActualEnd和UserActualEnd列。

    SELECT '1' as PPCONO,
           b.new_SalesrepId AS PPREP1,
           CONVERT(VARCHAR(8), a.UserActualEnd, 112) AS PPDATE,
           COUNT(b.new_SalesrepId) AS PPCOUNT
    FROM ActivityPointerBase AS a
    JOIN SystemUserExtensionBase AS b ON b.SystemUserId = a.OwnerId
    WHERE b.new_SalesrepId <> '99999999' 
    AND a.ActivityTypeCode = '4201' 
    AND b.new_SalesrepId IS NOT NULL 
    AND a.StateCode = '1' 
    AND CONVERT(DATE, a.UserActualEnd) = CONVERT(DATE, DATEADD(DAY, -1, GETDATE()))
    GROUP BY b.new_SalesrepId,
             CONVERT(VARCHAR(8), a.UserActualEnd, 112)
    ORDER BY b.new_SalesrepId ASC;
    

    解决方案3:重建用户本地时间

    构建一个系统,您可以从UTC时间戳重建用户本地时间。我将在下面概述一个简单的,但它绝不是测试,也不是重建它们的唯一方法。

    首先,您需要在UserSettingsBase表中使用ObserveDst列,因为某些状态遵循DST而某些状态不遵循,因此TimeZoneBias不足以重建用户本地时间。

      ObserveDst INT NOT NULL
    

    如果他们观察到DST,则包含值1,否则为0。

    其次,您将需要一个DST开始和结束时间戳表,因为他们已经随着时间的推移更改了定义,并且可能在将来再次这样做。我建议将一张桌子拆分成间隔。这样,您可以与其他表执行简单连接,而不是定义和调用每个记录操作的函数。

    CREATE TABLE Dst
    (
      BeginDT DATETIME NOT NULL,
      EndDT DATETIME NOT NULL,
      DstBias INT NOT NULL,
      PRIMARY KEY(BeginDT)
    );
    

    并在表上放置(BeginDT,EndDT,DstBias)索引。它可以包含这种情况下的所有列,因为它不会是一个非常大的表。 2014年,您将拥有以下记录:

    ('2014-01-01 00:00:00', '2014-03-09 02:00:00', 0)
    ('2014-03-09 02:00:00', '2014-11-02 03:00:00', 60)
    ('2014-11-02 03:00:00', '2015-01-01 00:00:00', 0)
    

    11月小时可能看起来有点奇怪,但此表的目的是从本地非DST时间转移到本地DST时间。和2014-11-02 03:00:00美国东部时间是2014-11-02 02:00:00美国东部时间。另外,请注意这些是闭合打开的时间间隔,这意味着包括第一个时间戳和最多但不包括最后一个时间戳。如果您在DST之前有历史日期,则可以将它们全部压缩到一个区间。你甚至可以加入每年的开始和结束时间间隔,只给你N年的N + 3条记录。

    美国交通部为选择遵守夏令时(http://www.dot.gov/regulations/daylight-saving-time)的州定义联邦法定日期。我建议您查看他们或其他合理的官方网站,了解您所需年份的正确日期。

    然后您只需加入Dst表并执行基本偏移计算。

    SELECT '1' as PPCONO,
           b.new_SalesrepId AS PPREP1,
           CONVERT(VARCHAR(8), CONVERT(DATE, DATEADD(MINUTE, d.DstBias*c.ObserveDst-c.TimeZoneBias, a.ActualEnd)), 112) AS PPDATE,
           COUNT(b.new_SalesrepId) AS PPCOUNT
    FROM ActivityPointerBase AS a
    JOIN SystemUserExtensionBase AS b ON b.SystemUserId = a.OwnerId
    JOIN UserSettingsBase AS c ON c.SystemUserId = b.SystemUserId
    JOIN Dst AS d ON DATEADD(MINUTE, -c.TimeZoneBias, a.ActualEnd) >= d.BeginDT AND DATEADD(MINUTE, -c.TimeZoneBias, a.ActualEnd) < d.EndDT
    WHERE b.new_SalesrepId <> '99999999' 
    AND a.ActivityTypeCode = '4201' 
    AND b.new_SalesrepId IS NOT NULL 
    AND a.StateCode = '1' 
    AND CONVERT(DATE, DATEADD(MINUTE, d.DstBias*c.ObserveDst-c.TimeZoneBias, a.ActualEnd)) = CONVERT(DATE, DATEADD(DAY, -1, GETDATE()))
    GROUP BY b.new_SalesrepId,
             CONVERT(VARCHAR(8), CONVERT(DATE, DATEADD(MINUTE, d.DstBias*c.ObserveDst-c.TimeZoneBias, a.ActualEnd)), 112)
    ORDER BY b.new_SalesrepId ASC;