这是我使用的:
SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)
我在想可能有更好更优雅的方式。
要求:
- 它必须尽可能快(铸造越少越好)。
- 最终结果必须是
datetime
类型,而不是字符串。
这是我使用的:
SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)
我在想可能有更好更优雅的方式。
要求:
datetime
类型,而不是字符串。SQL Server 2008 及更高版本
在 SQL Server 2008 及更高版本中,最快的方法当然是Convert(date, @date)
. 如有必要,可以将其转换回 adatetime
或。datetime2
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 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)
SQL Server 2008 有一个新的日期数据类型,这将这个问题简化为:
SELECT CAST(CAST(GETDATE() AS date) AS datetime)
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)是由该杂志四月号的一位读者推荐的。据他介绍,它的性能可与上述第二种技术相媲美。
您的CAST
- FLOOR
-CAST
似乎已经是最佳方式,至少在 MS SQL Server 2005 上。
我见过的其他一些解决方案有一个字符串转换,就像Select Convert(varchar(11), getdate(),101)
它们一样,它慢了 10 倍。
请试试:
SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
SQL2005:我建议使用 cast 而不是 dateadd。例如,
select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)
在我的数据集上平均快10% ,比
select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)
(并且转换为 smalldatetime 仍然更快)