【问题标题】:How to remove the time portion of a datetime value (SQL Server)?如何删除日期时间值的时间部分(SQL Server)?
【发布时间】:2010-09-05 09:22:40
【问题描述】:

这是我使用的:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

我在想可能有更好更优雅的方式。

要求:

  • 必须尽可能快(投射越少越好)。
  • 最终结果必须是 datetime 类型,而不是字符串。

【问题讨论】:

    标签: sql-server datetime date-conversion


    【解决方案1】:

    SQL Server 2008 及更高版本

    在 SQL Server 2008 及更高版本中,最快的方法当然是Convert(date, @date)。如有必要,可以将其转换回 datetimedatetime2

    在 SQL Server 2005 及更早版本中真正最好的是什么?

    我看到关于在 SQL Server 中从日期截断时间最快的方法的说法不一致,有些人甚至说他们进行了测试,但我的经验有所不同。所以让我们做一些更严格的测试,让每个人都有脚本,这样如果我犯了任何错误,人们可以纠正我。

    浮动转换不准确

    首先,我不会将datetime 转换为float,因为它不能正确转换。您可以准确地执行时间删除操作,但我认为使用它不是一个好主意,因为它隐含地向开发人员传达这是一个安全操作,它不是。看看:

    declare @d datetime;
    set @d = '2010-09-12 00:00:00.003';
    select Convert(datetime, Convert(float, @d));
    -- result: 2010-09-12 00:00:00.000 -- oops
    

    这不是我们应该在我们的代码或在线示例中教给人们的东西。

    而且,这甚至不是最快的方法!

    证明 - 性能测试

    如果您想自己执行一些测试以了解不同方法的实际叠加效果,那么您将需要此设置脚本来进一步运行测试:

    create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
    declare @d datetime;
    set @d = DateDiff(Day, 0, GetDate());
    insert AllDay select @d;
    while @@ROWCOUNT != 0
       insert AllDay
       select * from (
          select Tm =
             DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
          from AllDay
       ) X
       where Tm < DateAdd(Day, 1, @d);
    exec sp_spaceused AllDay;  -- 25,920,000 rows
    

    请注意,这会在您的数据库中创建一个 427.57 MB 的表,运行大约需要 15-30 分钟。如果您的数据库很小并且设置为 10% 的增长,那么它需要的时间会比您先设置足够大的情况要长。

    现在是实际的性能测试脚本。请注意,不将行返回给客户端是有目的的,因为这在 2600 万行上非常昂贵,并且会隐藏方法之间的性能差异。

    绩效结果

    set statistics time on;
    -- (All queries are the same on io: logical reads 54712)
    GO
    declare
        @dd date,
        @d datetime,
        @di int,
        @df float,
        @dv varchar(10);
    
    -- Round trip back to datetime
    select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
    select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
    select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
    select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
    select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
    select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
    select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.
    
    -- Only to another type but not back
    select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
    select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
    select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
    select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
    select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
    select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
    select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
    GO
    set statistics time off;
    

    一些杂乱无章的分析

    关于此的一些注释。首先,如果只是执行 GROUP BY 或比较,则无需转换回 datetime。因此,您可以通过避免这种情况来节省一些 CPU,除非您需要最终值用于显示目的。您甚至可以 GROUP BY 未转换的值并将转换仅放在 SELECT 子句中:

    select Convert(datetime, DateDiff(dd, 0, Tm))
    from (select '2010-09-12 00:00:00.003') X (Tm)
    group by DateDiff(dd, 0, Tm)
    

    另外,看看数字转换只需要稍微多一点的时间就可以转换回datetime,但varchar 的转换几乎翻了一番?这揭示了查询中专门用于日期计算的 CPU 部分。有部分 CPU 使用率不涉及日期计算,在上述查询中这似乎接近 19875 毫秒。然后转换需要一些额外的金额,因此如果有两次转换,该金额大约会用完两次。

    更多检查表明,与Convert(, 112) 相比,Convert(, 101) 查询有一些额外的 CPU 开销(因为它使用更长的varchar?),因为第二次转换回date 的开销并不大作为初始转换为varchar,但使用Convert(, 112),它更接近相同的 20000 毫秒 CPU 基本成本。

    以下是我用于上述分析的 CPU 时间计算:

         method   round  single   base
    -----------  ------  ------  -----
           date   21324   19891  18458
            int   23031   21453  19875
       datediff   23782   23218  22654
          float   36891   29312  21733
    varchar-112  102984   64016  25048
    varchar-101  123375   65609   7843
    
    • round 是往返于datetime 的 CPU 时间。

    • single 是单次转换为备用数据类型(具有删除时间部分的副作用)的 CPU 时间。

    • base 是从single 中减去两次调用之间的差的计算结果:single - (round - single)。这是一个大概的数字,假设与该数据类型之间的转换和datetime 在任一方向上大致相同。看起来这个假设并不完美,但很接近,因为这些值都接近 20000 毫秒,只有一个例外。

    更有趣的是,基本成本几乎等于单个 Convert(date) 方法(它必须几乎是 0 成本,因为服务器可以在内部直接从前四个字节中提取整数天部分datetime 数据类型)。

    结论

    所以看起来单向varchar转换方法大约需要1.8μs,而单向DateDiff方法大约需要0.18μs。在我对 25,920,000 行总共 18458 毫秒的测试中,我将此基于最保守的“基本 CPU”时间,因此 23218 毫秒 / 25920000 = 0.18 微秒。明显的 10 倍改进似乎很多,但坦率地说,在您处理数十万行之前它非常小(617k 行 = 1 秒节省)。

    即使有这么小的绝对改进,在我看来,DateAdd 方法还是胜出,因为它是性能和清晰度的最佳组合。需要0.50000004 的“神奇数字”的答案总有一天会咬人(五个零或六个???),而且更难理解。

    附加说明

    当我有时间时,我会将0.50000004 更改为'12:00:00.003',看看效果如何。它被转换为相同的datetime 值,我发现它更容易记住。

    对于那些感兴趣的人,上述测试是在@@Version 返回以下内容的服务器上运行的:

    Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) Jul 9 2008 14:43:34 版权所有 (c) 1988-2008 Microsoft Corporation Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)

    【讨论】:

    • +1 顺便说一句,您在哪个版本的 SQL Server 上进行了测试?
    • 看起来你的桌子上有 singleround。另外,如果你使用char而不是varchar,在时间上有什么不同吗?
    • @Gabe 谢谢,已修复。 Char 似乎与 varchar 完全相同。
    • 在 Oracle 中有 select round(sysdate) from dual,我们在 Sql Server 中肯定需要它。
    • @Roman 如果您使用的是 SQL Server 2008 及更高版本,是的,转换为 date 数据类型是最快的,如我上面的测试所示。
    【解决方案2】:

    SQL Server 2008 有一个新的date data type,这将这个问题简化为:

    SELECT CAST(CAST(GETDATE() AS date) AS datetime)
    

    【讨论】:

    • 我错误地输入了 0218 而不是 2018 作为年份,DATEADD(DATEDIFF()) 方法来减少时间部分会引发异常。当我将结果转换回datetime2 时,您的方法运行良好select cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
    【解决方案3】:

    Itzik Ben-Gan 在DATETIME Calculations, Part 1(SQL Server 杂志,2007 年 2 月)中展示了执行这种转换的三种方法(从最慢到最快;第二种和第三种方法之间的差异很小):

    SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)
    
    SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)
    
    SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)
    

    您的技术(转换为 float)由该杂志 4 月号的一位读者推荐。据他介绍,它的性能与上面介绍的第二种技术相当。

    【讨论】:

    • 在我看来,转换为浮动并不是最好的。请see my answer
    • @Emtucifor 我同意第三种方法非常模糊,因为 0.50000004 值,但 它是最快的一种,您的测试证实了这一点。因此,它满足尽可能快的要求。
    • @Emtucifor 另外,这是我链接的文章中关于 0.50000004 值的内容:虽然这个表达式很短(而且很有效,我稍后会演示) , 我不得不说我对此感到不安。我不确定我能说出确切的原因——也许是因为它技术性太强,而且你看不到其中与日期时间相关的逻辑。
    • 如果我们要使用这种方法,我更喜欢SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime),因为它对我来说很有意义,而且更容易记住。
    • 这是现在 SQL 2008 中最快的:Convert(date, GetDate())
    【解决方案4】:

    您的CAST-FLOOR-CAST 似乎已经是最佳方式,至少在 MS SQL Server 2005 上是如此。

    我见过的其他一些解决方案有一个字符串转换,比如Select Convert(varchar(11), getdate(),101),它的速度是原来的 10 倍。

    【讨论】:

    • 我们在我们的一款产品中使用了 Michael Stum 建议的方法,效果非常好。
    • 这不是最佳方式,相当多。请在同一页面上查看my answer
    【解决方案5】:

    请尝试:

    SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
    

    【讨论】:

      【解决方案6】:

      SQL2005:我建议使用 cast 而不是 dateadd。例如,

      select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)
      
      在我的数据集上,

      平均比

      快 10%
      select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)
      

      (并且转换成 smalldatetime 更快)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-19
        • 2019-07-05
        • 1970-01-01
        • 2018-07-23
        相关资源
        最近更新 更多