Ответы:
Поскольку вы используете datetimeтип данных, вам необходимо понять, как сервер sql округляет данные даты и времени.
╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
║ 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 ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝
Используя приведенный ниже запрос, вы можете легко увидеть проблему округления, которую делает sql-сервер при использовании DATETIME
типа данных.
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) цифр точности?). Рекомендуется использовать значение, для которого точность не имеет значения, т. Е. < Начало следующей секунды, минуты, часа или дня по сравнению с <= концом предыдущей секунды, минуты, часа или дня.
Как уже упоминали некоторые другие в комментариях и других ответах на ваш вопрос, основная проблема заключается 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 - это влияет на саргнитивность, иначе вы не можете выполнять поиск по индексу, и это часто приводит к снижению производительности.
Для получения более подробной информации о правильном пути и неправильном пути к запросам даты ручки диапазона извлекает этот пост по Aaron Bertrand .
На прощание вы сможете сохранить свой исходный запрос, и он будет вести себя как нужно, если вы измените свой posted_date
столбец с a DATETIMEна a DATETIME2(3)
. Это позволило бы сэкономить место на сервере, повысить точность при той же точности, быть более совместимым со стандартами / переносимым и позволить легко регулировать точность / точность, если ваши потребности изменятся в будущем. Тем не менее, это только вариант, если вы используете SQL Server 2008 или новее.
Как небольшая мелочь 1/300
, вторая точность с, DATETIMEкажется, удерживается от UNIX для этого ответа StackOverflow . Sybase, который имеет общее наследие, имеет схожую 1/300
вторую точность в их типах DATETIME
иTIME
типах данных, но их младшие значащие цифры - это касание, отличающееся в «0», «3» и «6». По моему мнению, 1/300
точность в секунду и / или 3,33 мс является неудачным архитектурным решением, поскольку 4-байтовый блок для времени в DATETIMEтипе данных SQL Server мог бы легко поддерживать точность в 1 мс.
datetime3
добавить 70 (против 7) цифр точности? Рекомендуется использовать значение, для которого точность не имеет значения, т. Е. <Начало следующей секунды, минуты, часа или дня по сравнению с <= концом предыдущей секунды, минуты, часа или дня.
Неявное преобразование
Я предположил, что тип данных posts_date - Datetime. Однако не имеет значения, является ли тип на другой стороне Datetime, Datetime2 или просто Time, потому что строка (Varchar) будет неявно преобразована в Datetime.
С объявленной в качестве Datetime2 (или Time) posts_date 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 или Time
Datetime2 и времени интервалы времени являются 00:00:00.0000000
через 23:59:59.9999999
с точностью до 100 нс (последняя цифра при использовании с 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
может возвращать неожиданные значения, когда оно не округлено до того, которое, по вашему мнению, должно быть.Мы могли бы подумать, что изменение [posts_date] на Datetime2 и его более высокую точность может решить эту проблему, но это не поможет, потому что строка все еще преобразуется в Datetime. Однако, если добавлен приведение cast(2015-07-27 23:59:59.999' as datetime2)
, это работает нормально
Приведение и преобразование
Приведение может преобразовывать значение длиной до 3 цифр в Datetime или до 9 цифр в Datetime2 или Time и округлять его с правильной точностью.
Следует отметить, что Cast of Datetime2 и Time2 могут давать разные результаты:
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Это своего рода устраняет проблему, возникающую у datetime с приращениями 0, 3 и 7, хотя все же всегда лучше искать даты до первой наносекунды следующего дня (всегда 0: 00: 00.000).
Источник MSDN: дата и время (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.
что это обозначает?