Відповіді:
Оскільки ви використовуєте 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.Довідка:
DateTime2vs DateTime.: a. Для - переважних - більшості - реального використання - випадки, переваги DateTime2значно <витрат. Дивіться: stackoverflow.com/questions/1334143/… b. Тут не проблема кореня . Дивіться наступний коментар.
datetime370 (проти 7) цифр точності?). Найкраща практика - використовувати значення, де точність не має значення, тобто < початок наступної секунди, хвилини, години або дня проти <= кінець попередньої секунди, хвилини, години або дня.
Як згадували кілька інших у коментарях та інших відповідях на ваше запитання, основну проблему 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 мс.
datetime3додати 70 (проти 7) цифр точності? Найкраща практика - використовувати значення, де точність не має значення, тобто <початок наступної секунди, хвилини, години або дня проти <= кінець попередньої секунди, хвилини, години або дня.
Неявне перетворення
Я вважаю, що тип даних 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.9970: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)
Це округлення
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.що це означає?