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

lec*_*g92 21 sql-server datetime

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'
Run Code Online (Sandbox Code Playgroud)

但是结果中包含了今天posted_date 的记录:2015-07-28。我的数据库服务器不在我的国家。问题是什么 ?

Eri*_*rik 19

正如其他几个人在评论和其他问题的答案中提到的,核心问题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
Run Code Online (Sandbox Code Playgroud)

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

关于选项 3,可以在此处找到有关某些问题的非常好的解释:Cast to date is sargable but it is a good idea?

我不喜欢选项 2 和 5,因为.997小数秒将只是人们想要“修复”的另一个神奇数字。出于其他一些BETWEEN未被广泛接受的原因,您可能需要查看这篇文章

我不喜欢选项 4,因为为了比较目的将数据类型转换为字符串对我来说很脏。在 SQL Server 中避免使用它的一个更定性的原因是它会影响sargability,也就是您无法执行索引查找,这通常会导致性能下降

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

在分离时,您将能够保留原始查询,并且如果您将posted_date列从 a更改DATETIME为 a ,它将按预期运行DATETIME2(3)。这将节省服务器上的存储空间,在相同精度下为您提供更高的精度,更符合标准/便携性,并允许您在将来的需求发生变化时轻松调整精度/精度。但是,如果您使用 SQL Server 2008 或更新版本,这只是一个选项。

根据StackOverflow 的答案1/300,第二个准确度DATETIME似乎是 UNIX 的一个小问题。拥有共同遗产的Sybase1/300在它们的数据类型DATETIMETIME数据类型上具有相似的第二精度,但它们的最低有效数字在“0”、“3”和“6”处略有不同。在我看来1/300,秒和/或 3.33 毫秒的精度是一个不幸的架构决策,因为SQL Server数据类型中的时间4 字节块DATETIME可以轻松支持 1 毫秒的精度。


Kin*_*hah 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 ?
??????????????????????????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

使用下面的查询,您可以很容易地看到使用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
Run Code Online (Sandbox Code Playgroud)

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

DATETIME2自 SQL Server 2008 以来一直存在,所以开始使用它而不是DATETIME. 对于你的情况,你可以使用datetime23位小数精度datetime2(3)

使用的好处datetime2

  • 时间分量最多支持 7 个小数位,而datetime只支持 3 个小数位……因此您会看到舍入问题,因为默认情况下,以,或秒为增量舍入datetime最接近的。.003 seconds.000.003.007
  • datetime2比 更精确,datetime并且可以datetime2让您控制DATETIMEdatetime.

参考 :


Jul*_*eur 9

隐式转换

我认为posted_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.
Run Code Online (Sandbox Code Playgroud)

日期时间的时间范围

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

准确性

此外,日期时间值按 .000、.003 或 0.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,所以字符串被四舍五入并成为这个日期时间值: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 或 Time 类型

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

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

  • 日期时间0:0:00.00023:59:59.997
  • 日期时间 20: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`
Run Code Online (Sandbox Code Playgroud)
  • < 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),这可以正常工作

投射和转换

Cast 可以将最多 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 为增量的问题,尽管在第二天的 1 纳秒之前查找日期总是更好(总是 0:00:00.000)。

源 MSDN:日期时间 (Transact-SQL)


pap*_*zzo 6

这是四舍五入

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000
Run Code Online (Sandbox Code Playgroud)

.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'
Run Code Online (Sandbox Code Playgroud)

或者

where cast(posted_date as date) = '2015-07-27'
Run Code Online (Sandbox Code Playgroud)

请参阅此链接中的准确性
始终报告为 .000、.003、.007