如何删除日期时间值的时间部分(SQL Server)?


Answers:


116

SQL Server 2008及更高版本

在SQL Server 2008及更高版本中,当然最快的方法是Convert(date, @date)。可以将其强制转换为datetime或(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 ms。然后,转换需要一些额外的金额,因此,如果有两次转换,则该金额将用完大约两次。

更多检查发现,与相比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时间。

  • 是从减去计算single的两个调用之间的差:single - (round - single)。这是一个粗略的数字,它假定了与该数据类型之间的转换,并且datetime在任一方向上都大致相同。看起来这个假设并不完美,但很接近,因为所有值都接近20000 ms,只有一个例外。

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

结论

因此,单向varchar转换方法大约需要1.8μs,单向DateDiff方法大约需要0.18μs。我将这基于最保守的“基本CPU”时间,我总共测试了25,920,000行的18458 ms,因此23218 ms / 25920000 = 0.18μs。显然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)2008年7月9日版权所有(c)1988-2008 Windows NT 5.2(Build 3790:Service Pack 2)上的Microsoft Corporation Standard Edition


1
+1顺便说一下,您在哪个版本的SQL Server上对其进行了测试?
马丁·史密斯

1
看起来您的表中有一个单向和向后四舍五入的功能。另外,如果您使用char而不是,时间也会有所不同varchar吗?
加布

1
@Gabe谢谢,固定。Char似乎与varchar完全相同。
ErikE 2010年

在Oracle中select round(sysdate) from dual,我们肯定在Sql Server中需要它。
Denis Valeev 2010年

3
@Roman如果您使用的是SQL Server 2008及更高版本,可以,转换为date数据类型最快,如上面的测试所示。
ErikE

30

SQL Server 2008具有新的日期数据类型,此问题简化为:

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

1
我错误地输入了0218而不是2018年,因此DATEADD(DATEDIFF())削减时间的方法引发了异常。当我将结果转换回datetime2您的方法时,效果很好select cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
BernhardDöbler18

18

Itzik Ben-Gan在DATETIME Calculations,第1部分(SQL Server Magazine,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)

读者在杂志的四月号中提出了您的技巧(铸造成浮动)。据他介绍,它的性能可与上述第二种技术媲美。


1
在我看来,浮动不是最好的。请查看我的回答
ErikE 2010年

1
@Emtucifor我同意第三种方法由于值为0.50000004而非常晦涩,但是它是最快的一种,您的测试证实了。因此,它满足了尽可能快的要求。
Marek Grzenkowicz

1
@Emtucifor另外,这是我链接的文章中关于0.50000004值的内容:尽管此表达式很短(并且很有效,正如我稍后将演示的那样),但我不得不说我对此感到不安。我不确定我可以确切地说明原因-也许是因为它太技术性了,您看不到与日期时间相关的逻辑。
Marek Grzenkowicz

2
如果我们要使用此方法,则我宁愿选择SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)它,因为这对我来说意味着某种意义,而且更容易记住。
ErikE 2010年

6
现在这是SQL 2008中最快的:Convert(date, GetDate())
ErikE 2011年

12

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

我见过的其他一些解决方案具有字符串转换功能,例如Select Convert(varchar(11), getdate(),101),它们的速度要慢10倍。


1
我们在其中一种产品中使用了迈克尔·斯图姆(Michael Stum)所建议的方法,它的工作原理就像一种魅力。
克里斯·罗伯茨

3
这并不是最佳的方法。请在同一页面上查看我的答案
ErikE

4

请尝试:

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

1

SQL2005:我建议使用强制转换而不是dateadd。例如,

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

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

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

(并且强制转换为smalldatetime还是更快)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.