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

时间:2020-03-05 18:37:50  来源:igfitidea点击:

这是我使用的:

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

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

要求:

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

解决方案

回答

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

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

回答

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

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

回答

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)

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

回答

SQL Server 2008及更高版本

在SQL Server 2008及更高版本中,当然最快的方法是Convert(date,@date)。如有必要,可以将其转换回datetime或者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.57MB的表,并且将花费大约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使用率的某些部分不涉及日期计算,在上述查询中,这似乎接近19875ms。然后,转换需要一些额外的金额,因此,如果有两次转换,则该金额将用完大约两次。

更多检查发现,与" Convert(,112)"相比," Convert(,101)"查询具有一些额外的CPU开销(因为它使用了更长的" varchar"?),因为第二次返回" date"的转换没有它的成本不及最初转换为varchar的成本,但使用Convert(,112)则更接近相同的20000ms 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 ms,只有一个例外。

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

结论

因此,单向varchar转换方法大约需要1.8s,而DateDiff单向方法大约需要0.18s。我基于最保守的"基本CPU"时间来进行测试,总共测试了25,920,000行,总共18458ms,因此23218ms / 25920000 = 0.18s。明显的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   Copyright (c) 1988-2008 Microsoft Corporation  Standard Edition on Windows NT 5.2  (Build 3790: Service Pack 2)