为什么我的查询搜索日期时间不匹配?


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

但是结果中包含的记录的发布日期为今天:2015-07-28。我的数据库服务器不在我的国家/地区。问题是什么 ?

Answers:


16

由于您使用的是datetime数据类型,因此需要了解sql server如何舍入日期时间数据。

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

在此处输入图片说明

使用下面的查询,您可以轻松地看到使用DATETIME数据类型时sql server进行舍入的问题。

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

在此处输入图片说明 点击放大

DATETIME2从SQL Server 2008开始就存在了,所以开始使用它而不是DATETIME。对于你的情况,你可以使用datetime23位小数精度datetime2(3)

使用的好处datetime2

  • 支持最多7个小数位的时间分量,而datetime仅支持3个小数位..因此,您会看到舍入问题,因为默认情况下,以或秒为单位datetime对最接近的位.003 seconds进行舍入。.000.003.007
  • datetime2比精确得多,datetime并且datetime2可以控制DATETIME相对datetime

参考:


1
gives you control of DATE and TIME as opposed to datetime.这意味着什么?
nurettin

回覆。使用DateTime2DateTime:一。对于-的- 庞大的-多数 -的- 现实-世界 -使用-的情况下,好处DateTime2很多<成本。请参阅:stackoverflow.com/questions/1334143/…b 。这不是这里的根本问题。请参阅下一条评论。
汤姆

这里的根本问题(我敢打赌大多数高级开发人员都会同意)不是日期时间范围比较的包含结束日期时间精度不够,而是使用包含(相对于专有)期间。这就像检查与Pi的相等性一样,总是有#之一具有>或<精度的可能性(即,如果添加datetime3了70(相对于7)个精度的数字怎么办?)。最好的做法是使用其中的精度并不重要,即,<a值开始下一秒,分钟,小时或天对<=所述的现有秒,分钟,小时或天的。
汤姆

18

正如其他一些人在评论和对问题的其他答案中提到的那样,核心问题2015-07-27 23:59:59.999正在2015-07-28 00:00:00.000由SQL Server进行处理。每文档DATETIME

时间范围-00:00:00至23:59:59.997

请注意,时间范围不能.999。在文档的下半部分,它指定SQL Server用于最低有效位的舍入规则。

该表显示了四舍五入规则

请注意,最低有效位只能具有三个潜在值之一:“ 0”,“ 3”或“ 7”。

您可以使用多种解决方案/解决方法。

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

在上面介绍的五个选项中,我认为选项1和3是唯一可行的选项。它们清楚地传达了您的意图,并且如果您更新数据类型也不会中断。如果您使用的是SQL Server 2008或更高版本,我认为选项3应该是您的首选方法。如果您可以从使用DATETIME数据类型更改DATEposted_date列的数据类型,则尤其如此。

关于选项3,可以在此处找到有关某些问题的很好的解释:到目前为止,可以进行强制转换,但这是个好主意吗?

我不喜欢选项2和5,因为.997小数秒只是人们想要“修复”的另一个魔幻数字。出于更多原因,为什么BETWEEN不被广泛接受,您可能想要查看这篇文章

我不喜欢选项4,因为将数据类型转换为字符串以进行比较对我来说很脏。从质量上讲,在SQL Server中避免使用它的原因是,它会影响可存储性,即您无法执行索引查找,这通常会导致性能降低。

有关正确的方式和错误的方式来处理日期范围查询的详细信息结帐这个职位阿龙贝特朗

分手时,您将可以保留原始查询,并且如果您将posted_date列从a 更改DATETIME为a ,它将可以按预期运行DATETIME2(3)。这样可以节省服务器上的存储空间,以相同的精度为您提供更高的精度,更符合标准/可移植,并且如果将来需求发生变化,还可以轻松调整精度/精度。但是,这仅是在使用SQL Server 2008或更高版本时的选择。

有点琐事的是1/300根据这个StackOverflow的回答,第二个精度DATETIME似乎是UNIX的一个保留。具有共同遗产的 Sybase 在其数据类型上具有类似的第二精度,但是它们的最低有效位在“ 0”,“ 3”和“ 6”处的触摸方式有所不同。在我看来,秒精度和/或3.33ms的精度是一个不幸的体系结构决策,因为SQL Server 数据类型中4字节的时间块很容易支持1ms的精度。1/300DATETIMETIME1/300DATETIME


是的,但核心的 “核心问题”没有使用选项1(例如,使用过去或将来可能出现的数据类型的精度可能影响结果的任何包含性(相对于排他性)范围终值)。就像检查与Pi的相等性一样,总是有一个#具有>或<精度(除非两者都被四舍五入到最低的通用精度)。如果datetime3加上70(vs. 7)精度数字怎么办?最佳做法是使用精度无关紧要的值,即<下一个秒,分钟,小时或天的开始与<=前一个秒,分钟,小时或天的结束。
汤姆

9

隐式转换

我以为postd_date数据类型为Datetime。但是,另一边的类型是Datetime,Datetime2还是仅仅是Time都没有关系,因为字符串(Varchar)将隐式转换为Datetime。

在将posted_date声明为Datetime2(或Time)的情况下,posted_date <= '2015-07-27 23:59:59.99999'where子句将失败,因为它虽然23:59:59.99999是有效的Datetime2值,但它不是有效的Datetime值:

 Conversion failed when converting date and/or time from character string.

日期时间的时间范围

Datetime的时间范围是00:00:00到23:59:59.997。因此23:59:59.999超出范围,必须向上或向下舍入到最接近的值。

准确性

此外,日期时间值以.000,.003或.007秒的增量舍入。(即000、003、007、010、013、017、020,...,997)

2015-07-27 23:59:59.999在此范围内的值不是这种情况:2015-07-27 23:59:59.9972015-07-28 0:00:00.000

此范围对应于最接近的前后选项,均以.000,.003或.007结尾。

向上或向下取整

由于它比2015-07-28 0:00:00.000(+1与-2)更接近2015-07-27 23:59:59.997,因此该字符串将四舍五入并变为以下Datetime值:2015-07-28 0:00:00.000

如果使用上限2015-07-27 23:59:59.998(如.995,.996,.997,.998),则该值将四舍五入到2015-07-27 23:59:59.997您所查询的值。但是,这不是一个解决方案,而只是一个幸运的价值。

Datetime2或时间类型

DATETIME2和时间的时间范围是00:00:00.0000000通过23:59:59.9999999具有100ns的精度(具有7位精度使用时最后一位)。

但是,Datetime(3)范围与Datetime范围不同:

  • 日期时间0:0:00.00023:59:59.997
  • 日期时间2 0:0:00.00000000023:59:59.999

最后,寻找第二天以下的日期比低于或等于您认为是一天中最后一部分的日期更安全。这主要是因为您知道第二天总是从0:00:00.000开始,但是不同的数据类型在一天结束时可能没有相同的时间:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000将为您提供准确的结果,是最佳选择
  • <= 2015-07-27 23:59:59.xxx 如果未将其四舍五入到您认为应该的值,则可能会返回意外值。
  • 应避免转换为日期和使用函数,因为这会限制索引的使用

我们可以认为将[posted_date]更改为Datetime2及其更高的精度可以解决此问题,但由于该字符串仍会转换为Datetime,因此它无济于事。但是,如果添加了强制转换cast(2015-07-27 23:59:59.999' as datetime2),则效果很好

转换并转换

强制转换可以将最多3位数字的值转换为Datetime或将最多9位数字的值转换为Datetime2或Time,并将其舍入为正确的精度。

请注意,Datetime2和Time2的Cast可能会给出不同的结果:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) 向上舍入到2015-05-03 00:00:00.0000000(值大于999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23:59:59.9999999

它虽然解决了日期时间以0、3和7递增的问题,但始终最好在第二天的第一毫微秒(总是0:00:00.000)之前查找日期。

源MSDN:datetime(Transact-SQL)


6

四舍五入

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998,.997,.996,.995均已铸造/四舍五入为.997

应该使用

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

要么

where cast(posted_date as date) = '2015-07-27'

在此链接中查看准确性
始终报告为.000,.003,.007


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.