AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 169195
Accepted
Mazhar
Mazhar
Asked: 2017-04-06 05:05:02 +0800 CST2017-04-06 05:05:02 +0800 CST 2017-04-06 05:05:02 +0800 CST

T-SQL 夏令时查找表 - 性能不佳的表值函数

  • 772

我为 GMT 区域创建了一个“夏令时”查找日历表。我用来查询表以从 UTC 日期时间返回本地日期时间的函数性能不佳。

任何有助于改善这一点的帮助,包括改变 TVF 的编码方式,都将不胜感激。

该函数将用于可以频繁返回 1m+ 行的查询。该函数用于查询包含行程数据的仓库表。

行程的开始和结束日期时间存储在 UTC 中,上面的函数用于将它们转换为本地时间。一位离开公司很久的开发人员编写了一个将 UTC 时间转换为本地时间的标量函数。我的任务是使用日历表和 TVF 重写该函数,因为 TVF 应该比标量函数表现更好

没有功能:

SQL Server Execution Times:    CPU time = 4633 ms,  elapsed time = 4909 ms.

没有功能的执行计划

具有以下功能:

SQL Server Execution Times:    CPU time = 20795 ms,  elapsed time = 21176 ms.

具有功能的执行计划

这是表格的示例输出

CREATE TABLE dbo.DSTLookup 
(
     [Id] int, 
     [Tzid] int, 
     [DT_WhenSwitch] datetime, 
     [DSTOffSetSeconds] int, 
     [GMTOffSetSeconds] int 
)

INSERT INTO dbo.DSTLookup
VALUES (29, 2, N'2014-03-30T01:00:00', 3600, 0), 
       (30, 2, N'2014-10-26T02:00:00', 0, 0), 
       (31, 2, N'2015-03-29T01:00:00', 3600, 0), 
       (32, 2, N'2015-10-25T02:00:00', 0, 0), 
       (33, 2, N'2016-03-27T01:00:00', 3600, 0), 
       (34, 2, N'2016-10-30T02:00:00', 0, 0), 
       (35, 2, N'2017-03-26T01:00:00', 3600, 0), 
       (36, 2, N'2017-10-29T02:00:00', 0, 0), 
       (37, 2, N'2018-03-25T01:00:00', 3600, 0), 
       (38, 2, N'2018-10-28T02:00:00', 0, 0)

这是 TVF:

CREATE FUNCTION dbo.FN_GetLocalTime_FromUTC_BasedOnTZId 
     (@StartDateTime DATETIME, @EndDateTime DATETIME, @Tzid INT)
/*=========================================================================
*   2017-03-27
*   Returns local time from UTC time based on timeZoneId
*
==========================================================================*/
RETURNS TABLE 
AS
    RETURN
        (
         WITH cteStartDate AS
         (
            SELECT
                RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
                D.DSTOffSetSeconds 's_DST_OffSet',
                D.GMTOffSetSeconds 's_GMT_OffSet'
            FROM
                dbo.DSTLookup D
            WHERE
                D.DT_WhenSwitch <= @StartDateTime
                AND D.Tzid = @Tzid
         ),
         cteEndDate AS
         (
             SELECT
                 RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
                 D.DSTOffSetSeconds 'e_DST_OffSet',
                 D.GMTOffSetSeconds 'e_GMT_OffSet'
             FROM
                 dbo.DSTLookup D
             WHERE
                 D.DT_WhenSwitch <= @EndDateTime
                 AND D.Tzid = @Tzid
         ),
         cteConvertStartDate AS
         (
              SELECT
                  DATEADD(SECOND, (COALESCE(S.s_DST_OffSet, 0) + COALESCE(S.s_GMT_OffSet, 0)), @StartDateTime) 'LocalStartDateTime'
              FROM
                  cteStartDate S
              WHERE
                  S.RN = 1
         ),
         cteConvertEndDate AS
         (
              SELECT
                  DATEADD(SECOND, (COALESCE(E.e_DST_OffSet, 0) + COALESCE(E.e_GMT_OffSet, 0)), @EndDateTime)    'LocalEndDateTime'
              FROM
                  cteEndDate E
              WHERE
                  E.RN = 1
         )
         SELECT
             S.LocalStartDateTime, E.LocalEndDateTime
         FROM
             cteConvertStartDate S, cteConvertEndDate E
);
GO

查询 TVF:

SELECT * 
FROM dbo.FN_GetLocalTime_FromUTC_BasedOnTzId
    ('2017-03-27 10:00:30', '2017-03-27 10:15:54', 2);

执行计划遵循 Max 的建议以包含主键。

sql-server sql-server-2008-r2
  • 2 2 个回答
  • 1028 Views

2 个回答

  • Voted
  1. Joe Obbish
    2017-04-06T10:53:26+08:002017-04-06T10:53:26+08:00

    如果定义一个唯一行,我建议按这两列对Tzid表进行聚类。如果需要,您可以将这些列设为主键,也可以将它们设为聚集索引。DT_WhenSwitchdbo.DSTLookup

    CREATE TABLE dbo.DSTLookup 
    (
         [Id] int, 
         [Tzid] int, 
         [DT_WhenSwitch] datetime, 
         [DSTOffSetSeconds] int, 
         [GMTOffSetSeconds] int 
    );
    
    CREATE CLUSTERED INDEX CI_DSTLookup ON dbo.DSTLookup ([Tzid], [DT_WhenSwitch]); -- new
    
    INSERT INTO dbo.DSTLookup
    VALUES (29, 2, N'2014-03-30T01:00:00', 3600, 0), 
           (30, 2, N'2014-10-26T02:00:00', 0, 0), 
           (31, 2, N'2015-03-29T01:00:00', 3600, 0), 
           (32, 2, N'2015-10-25T02:00:00', 0, 0), 
           (33, 2, N'2016-03-27T01:00:00', 3600, 0), 
           (34, 2, N'2016-10-30T02:00:00', 0, 0), 
           (35, 2, N'2017-03-26T01:00:00', 3600, 0), 
           (36, 2, N'2017-10-29T02:00:00', 0, 0), 
           (37, 2, N'2018-03-25T01:00:00', 3600, 0), 
           (38, 2, N'2018-10-28T02:00:00', 0, 0);
    

    这样做的原因是它将允许非常快速的单个行查找。对于针对要过滤的表的两个查询,并按降序[Tzid]查找第一个值。[DT_WhenSwitch]使用正确的聚集索引获取该行可以是单个聚集索引查找。

    为了获得我想要的计划,我将使用APPLY和TOP运算符稍微简化 TVF。我还想让优化器明白我每次只返回一行。这是一种实现:

    CREATE FUNCTION dbo.FN_GetLocalTime_FromUTC_BasedOnTZId 
         (@StartDateTime DATETIME, @EndDateTime DATETIME, @Tzid INT)
    /*=========================================================================
    *   2017-03-27
    *   Returns local time from UTC time based on timeZoneId
    *
    ==========================================================================*/
    RETURNS TABLE 
    WITH SCHEMABINDING
    AS
    RETURN
    (
            SELECT
                   DATEADD(SECOND, (COALESCE(S.s_DST_OffSet, 0) + COALESCE(S.s_GMT_OffSet, 0)), @StartDateTime) 'LocalStartDateTime'
                 , DATEADD(SECOND, (COALESCE(E.e_DST_OffSet, 0) + COALESCE(E.e_GMT_OffSet, 0)), @EndDateTime)   'LocalEndDateTime'
            FROM (SELECT 1 t) t
            OUTER APPLY (
                SELECT TOP 1 
                    D.DSTOffSetSeconds 's_DST_OffSet',
                    D.GMTOffSetSeconds 's_GMT_OffSet'
                FROM dbo.DSTLookup D
                WHERE D.DT_WhenSwitch <= @StartDateTime AND D.Tzid = @Tzid
                ORDER BY D.DT_WhenSwitch DESC
            ) s
            OUTER APPLY (
                SELECT TOP 1  
                     D.DSTOffSetSeconds 'e_DST_OffSet',
                     D.GMTOffSetSeconds 'e_GMT_OffSet'
                 FROM dbo.DSTLookup D
                 WHERE D.DT_WhenSwitch <= @EndDateTime AND D.Tzid = @Tzid
                 ORDER BY D.DT_WhenSwitch DESC
            ) e
    );
    

    这是问题中示例查询的查询计划:

    在此处输入图像描述

    正如预期的那样,我们只对聚集索引进行两次查找:

    (1 行受影响)

    表“DSTLookup”。扫描计数 2,逻辑读取 4,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。

    SQL Server 执行时间:

    CPU 时间 = 0 毫秒,经过时间 = 1 毫秒。

    我无法针对 SQL Server 2008 进行测试,但我认为该语法可以在该平台上运行。SQL Server 2014 的数据库小提琴。

    • 4
  2. Best Answer
    Hannah Vernon
    2017-04-06T05:22:47+08:002017-04-06T05:22:47+08:00

    WITH SCHEMABINDING通过添加到RETURNS TABLE子句使您的函数成为模式绑定表值函数。

    所以:

    CREATE FUNCTION dbo.FN_GetLocalTime_FromUTC_BasedOnTZId 
         (@StartDateTime DATETIME, @EndDateTime DATETIME, @Tzid INT)
    /*=========================================================================
    *   2017-03-27
    *   Returns local time from UTC time based on timeZoneId
    *
    ==========================================================================*/
    RETURNS TABLE 
    WITH SCHEMABINDING
    AS
        RETURN
            (
             WITH cteStartDate AS
             (
                SELECT
                    RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
                    D.DSTOffSetSeconds 's_DST_OffSet',
                    D.GMTOffSetSeconds 's_GMT_OffSet'
                FROM
                    dbo.DSTLookup D
                WHERE
                    D.DT_WhenSwitch <= @StartDateTime
                    AND D.Tzid = @Tzid
             ),
             cteEndDate AS
             (
                 SELECT
                     RN = ROW_NUMBER() OVER (ORDER BY D.Id DESC),
                     D.DSTOffSetSeconds 'e_DST_OffSet',
                     D.GMTOffSetSeconds 'e_GMT_OffSet'
                 FROM
                     dbo.DSTLookup D
                 WHERE
                     D.DT_WhenSwitch <= @EndDateTime
                     AND D.Tzid = @Tzid
             ),
             cteConvertStartDate AS
             (
                  SELECT
                      DATEADD(SECOND, (COALESCE(S.s_DST_OffSet, 0) + COALESCE(S.s_GMT_OffSet, 0)), @StartDateTime) 'LocalStartDateTime'
                      , S.RN
                  FROM
                      cteStartDate S
                  WHERE
                      S.RN = 1
             ),
             cteConvertEndDate AS
             (
                  SELECT
                      DATEADD(SECOND, (COALESCE(E.e_DST_OffSet, 0) + COALESCE(E.e_GMT_OffSet, 0)), @EndDateTime)    'LocalEndDateTime'
                      , E.RN
                  FROM
                      cteEndDate E
                  WHERE
                      E.RN = 1
             )
             SELECT
                 S.LocalStartDateTime, E.LocalEndDateTime
             FROM
                 cteConvertStartDate S
                 INNER JOIN cteConvertEndDate E ON S.RN = E.RN
    );
    

    这允许查询处理器“内联”函数。这允许进行多项优化,其中最重要的是能够正确理解函数中引用的对象的统计信息。

    dbo.DSTLookup向表中添加聚簇索引。这允许查询执行查找而不是扫描。对于示例数据中的行数,这可能不会产生很大的差异,但对于您的真实表,它可能会产生很大的差异。

    由于您有一个Id似乎是单调递增整数的列,也许这是用作聚集主键的一个很好的候选键:

    CREATE TABLE dbo.DSTLookup 
    (
         [Id] int
            CONSTRAINT PK_DSTLookup
            PRIMARY KEY CLUSTERED, 
         [Tzid] int, 
         [DT_WhenSwitch] datetime, 
         [DSTOffSetSeconds] int, 
         [GMTOffSetSeconds] int 
    );
    

    我会考虑根据您的 TVF 添加以下索引:

    CREATE INDEX IX_DSTLookup_001
    ON dbo.DSTLookup (DT_WhenSwitch, Tzid)
    INCLUDE (DSTOffSetSeconds, GMTOffSetSeconds);
    
    • 3

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve