Чому дата пошуку мого запиту не відповідає?


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

Відповіді:


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.що це означає?
Нуреттін

Re. використання DateTime2vs DateTime.: a. Для - переважних - більшості - реального використання - випадки, переваги DateTime2значно <витрат. Дивіться: stackoverflow.com/questions/1334143/… b. Тут не проблема кореня . Дивіться наступний коментар.
Том

Основна проблема тут (як я вважаю, що більшість старших розробників погодиться) - недостатня точність у порівняльному завершенні дати та часу порівняння діапазону дат і часу, а скоріше використання інклюзивного (проти ексклюзивного) одного періоду. Це як перевірка рівності Pi, завжди є можливість, що в одному з # є> або <точність (тобто що робити, якщо додано datetime370 (проти 7) цифр точності?). Найкраща практика - використовувати значення, де точність не має значення, тобто < початок наступної секунди, хвилини, години або дня проти <= кінець попередньої секунди, хвилини, години або дня.
Том

18

Як згадували кілька інших у коментарях та інших відповідях на ваше запитання, основну проблему 2015-07-27 23:59:59.999округляє 2015-07-28 00:00:00.000SQL Server. Відповідно до документації для DATETIME:

Діапазон часу - з 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стовпець з " DATETIMEна" 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 мс.


Так, але основна "основна проблема" не використовує варіант 1 (наприклад, використання будь-якого інклюзивного (проти ексклюзивного) кінцевого значення діапазону, де точність минулих чи потенційних типів майбутніх даних може вплинути на результати). Це як перевірка рівності Pi, завжди можлива одна # має> або <точність (якщо обидва не попередньо округлені до найменшої загальної точності). Що робити, якщо datetime3додати 70 (проти 7) цифр точності? Найкраща практика - використовувати значення, де точність не має значення, тобто <початок наступної секунди, хвилини, години або дня проти <= кінець попередньої секунди, хвилини, години або дня.
Том

9

Неявне перетворення

Я вважаю, що тип даних data_date - це Datetime. Однак не має значення, чи є тип на іншій стороні Datetime, Datetime2 або просто Time, оскільки рядок (Varchar) буде неявно перетворений у Datetime.

Якщо posts_date оголошений як Datetime2 (або Time), posted_date <= '2015-07-27 23:59:59.99999'пункт де виходить з ладу, оскільки хоч 23:59:59.99999це дійсне значення Datetime2, це не є дійсним значенням Datetime:

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

Діапазон часу для дати

Діапазон часу в режимі Datetime - від 00:00 до 23:59: 59.997. Тому 23: 59: 59.999 знаходиться поза межами діапазону і має бути закруглено вгору або вниз до найближчого значення.

Точність

Окрім того, значення Datetime округляються з кроком до .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 з точністю 100ns (остання цифра при використанні з точністю 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 може повернути несподівані значення, коли воно не округлене до того, що, на вашу думку, повинно бути.
  • Необхідно уникати перетворення на дату та використання функції, оскільки це обмежує використання індексів

Ми можемо подумати, що зміна [posted_date] на Datetime2 та її більш висока точність можуть вирішити цю проблему, але це не допоможе, оскільки рядок все ще перетворений на Datetime. Однак, якщо додано акторський склад cast(2015-07-27 23:59:59.999' as datetime2), це працює чудово

У ролях і конвертувати

Cast може конвертувати значення з трьома цифрами у 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

Це своєрідне виправлення проблеми, пов’язане з тимчасовим часом, з кроком 0, 3 і 7, хоча все одно завжди краще шукати дати до 1-ї нано секунди наступного дня (завжди 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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.