85

这是我使用的:

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

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

要求:

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

6 回答 6

117

SQL Server 2008 及更高版本

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

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

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

浮点转换不准确

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

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 ms 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是往返返回的 CPU 时间datetime

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

  • basesingle是从两次调用之间的差值中减去的计算: 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)

于 2010-09-12T22:57:35.493 回答
30

SQL Server 2008 有一个新的日期数据类型,这将这个问题简化为:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)
于 2008-08-06T06:44:32.600 回答
18

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)

您的技术(casting to float)是由该杂志四月号的一位读者推荐的。据他介绍,它的性能可与上述第二种技术相媲美。

于 2008-08-06T08:06:19.720 回答
12

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

我见过的其他一些解决方案有一个字符串转换,就像Select Convert(varchar(11), getdate(),101)它们一样,它慢了 10 倍。

于 2008-08-05T20:12:29.037 回答
4

请试试:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
于 2013-06-29T09:49:05.130 回答
1

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

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

在我的数据集上平均10% ,比

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(并且转换为 smalldatetime 仍然更快)

于 2014-11-05T04:26:40.977 回答