Answers:
由于您使用的是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
。对于你的情况,你可以使用datetime2
与3位小数精度如datetime2(3)
。
使用的好处datetime2
:
datetime
仅支持3个小数位..因此,您会看到舍入问题,因为默认情况下,以或秒为单位datetime
对最接近的位.003 seconds
进行舍入。.000
.003
.007
datetime2
比精确得多,datetime
并且datetime2
可以控制DATE
和TIME
相对datetime
。参考:
DateTime2
与DateTime
:一。对于-的- 庞大的-多数 -的- 现实-世界 -使用-的情况下,好处DateTime2
很多<成本。请参阅:stackoverflow.com/questions/1334143/…b 。这不是这里的根本问题。请参阅下一条评论。
datetime3
了70(相对于7)个精度的数字怎么办?)。最好的做法是使用其中的精度并不重要,即,<a值开始下一秒,分钟,小时或天对<=所述的端现有秒,分钟,小时或天的。
正如其他一些人在评论和对问题的其他答案中提到的那样,核心问题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数据类型更改DATE为posted_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/300
DATETIME
TIME
1/300
DATETIME
datetime3
加上70(vs. 7)精度数字怎么办?最佳做法是使用精度无关紧要的值,即<下一个秒,分钟,小时或天的开始与<=前一个秒,分钟,小时或天的结束。
隐式转换
我以为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.997
和2015-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.000
到23:59:59.997
0:0:00.000000000
至23: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)
四舍五入
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
select * from A where date(posted_date) = '2015-07-27'
'DATE' is not a recognized built-in function name.
gives you control of DATE and TIME as opposed to datetime.
这意味着什么?