Почему дата и время моего запроса не совпадают?

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. Мой сервер базы данных не в моей стране. В чем проблема ?

lecuong92
источник

Ответы:

16

Поскольку вы используете 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:

  • Поддержка до 7 знаков после запятой для компонента времени против datetimeподдержки только 3 знаков после запятой .. и , следовательно , вы видите скругление проблемы , так как по умолчанию datetimeраундов ближайшим .003 secondsс шагом .000, .003или .007секундами.
  • datetime2гораздо точнее, чем datetimeи datetime2дает вам контроль DATEи TIMEв отличие от datetime.

Ссылка :

Кин Шах
источник
1
gives you control of DATE and TIME as opposed to datetime.что это обозначает?
nurettin
Число рейнольдса используя DateTime2против DateTime: а. Для прецедентов, применяемых в подавляющем большинстве случаев в реальном мире , преимущества DateTime2намного выше затрат. См .: stackoverflow.com/questions/1334143/… b. Это не главная проблема здесь. Смотрите следующий комментарий.
Том
Основная проблема здесь (как я держу пари, что большинство старших разработчиков согласятся с этим) заключается не в недостаточной точности во включающем окончании даты-времени сравнения диапазонов дат и времени, а в использовании включающего (исключающего) одного периода. Это все равно, что проверять равенство Пи, всегда есть вероятность, что у одного из # есть> или <точность (то есть, что если datetime3добавить 70 (против 7) цифр точности?). Рекомендуется использовать значение, для которого точность не имеет значения, т. Е. < Начало следующей секунды, минуты, часа или дня по сравнению с <= концом предыдущей секунды, минуты, часа или дня.
Том
18

Как уже упоминали некоторые другие в комментариях и других ответах на ваш вопрос, основная проблема заключается 2015-07-27 23:59:59.999в том, чтобы 2015-07-28 00:00:00.000SQL 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 мс.

Erik
источник
Да, но основная «основная проблема» не использует вариант 1 (например, использование любого включающего (против исключительного) конечного значения диапазона, в котором точность прошлых или потенциальных будущих типов данных может повлиять на результаты). Это все равно, что проверять равенство Пи, всегда возможно, чтобы один имел> точность> или <точность (если только оба не были предварительно округлены до наименьшей общей точности). Что если datetime3добавить 70 (против 7) цифр точности? Рекомендуется использовать значение, для которого точность не имеет значения, т. Е. <Начало следующей секунды, минуты, часа или дня по сравнению с <= концом предыдущей секунды, минуты, часа или дня.
Том
9

Неявное преобразование

Я предположил, что тип данных 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
  • Datetime2 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 может возвращать неожиданные значения, когда оно не округлено до того, которое, по вашему мнению, должно быть.
  • Следует избегать преобразования в Date и использования функции, поскольку это ограничивает использование индексов.

Мы могли бы подумать, что изменение [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)

Жюльен Вавассер
источник
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

папараццо
источник
-2
select * from A where date(posted_date) = '2015-07-27' 
Хайме А. Арвизу
источник
1
'DATE' is not a recognized built-in function name.
Майкл Грин