Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

DATEDIFF() връща грешни резултати в SQL Server? Прочети това.

Ако получавате някои наистина странни резултати, когато използвате DATEDIFF() функция в SQL Server и сте убедени, че функцията съдържа грешка, не си късайте косата още. Вероятно не е грешка.

Има сценарии, при които резултатите, произведени от тази функция, могат да бъдат доста странни. И ако не разбирате как всъщност работи функцията, резултатите ще изглеждат напълно погрешни.

Надяваме се, че тази статия може да помогне да се изясни как DATEDIFF() функцията е проектирана да работи и предоставя някои примерни сценарии за това къде резултатите ви може да не са такива, каквито бихте очаквали.

Пример 1 – 365 дни не винаги са година

Въпрос: Кога е 365 дни не на година?

Отговор: Когато използвате DATEDIFF() разбира се!

Ето пример, в който използвам DATEDIFF() за да върнете броя на дните между две дати и след това броя на годините между същите две дати.

DECLARE @startdate datetime2 ='2016-01-01 00:00:00.0000000', @enddate datetime2 ='2016-12-31 23:59:59.9999999';ИЗБЕРЕТЕ @stardatdate (ден, ден, ден) , DATEDIFF(година, @startdate, @enddate) Години;

Резултат:

<пред>+--------+--------+| Дни | Години ||-------+--------|| 365 | 0 |+--------+--------+

Ако смятате, че този резултат е грешен и това DATEDIFF() очевидно има грешка, четете нататък – не всичко е както изглежда.

Вярвате или не, това всъщност е очакваният резултат. Този резултат е точно в съответствие с начина на DATEDIFF() е проектиран да работи.

Пример 2 – 100 наносекунди =1 година?

Нека го вземем по друг начин.

DECLARE @startdate datetime2 ='2016-12-31 23:59:59.9999999', @enddate datetime2 ='2017-01-01 00:00:00.0000000';ИЗБЕРЕТЕ @stardatdate, година, година , DATEDIFF(тримесечие, @startdate, @enddate) Тримесечие, DATEDIFF(месец, @startdate, @enddate) Месец, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Ден, DATEDIFF (седмица, @startdate, @enddate) Седмица, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(минута, @startdate, @enddate) Минута, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(милисекунда , @startdate, @enddate) Милисекунда, DATEDIFF(микросекунда, @startdate, @enddate) Микросекунда, DATEDIFF(наносекунда, @startdate, @enddate) Наносекунда;

Резултати (показани с вертикален изход):

<пред>Година | 1 тримесечие | 1 месец | 1DOY | 1 ден | 1 седмица | 1 час | 1 минута | 1 секунда | 1 милисекунда | 1 микросекунда | 1 наносекунда | 100

Има само сто наносекунди (.0000001 секунда) разлика между двете дати/часа, но получаваме абсолютно същия резултат за всяка част от датата, с изключение на наносекунди.

Как може да се случи това? Как може да има 1 микросекунда разлика и 1 година разлика и двете едновременно? Да не говорим за всички дати между тях?

Може да изглежда лудо, но и това не е грешка. Тези резултати са точно в съответствие с начина на DATEDIFF() трябва да работи.

И за да направим нещата още по-объркващи, бихме могли да получим различни резултати в зависимост от типа данни. Но скоро ще стигнем до това. Първо нека разгледаме как DATEDIFF() функцията наистина работи.

Действителната дефиниция на DATEDIFF()

Причината да получаваме резултатите, които правим, е, че DATEDIFF() функцията се дефинира, както следва:

Тази функция връща броя (като цяло число със знак) на посочените граници на датата, пресечени между посочената начална дата и крайна дата .

Обърнете специално внимание на думите „прекрачени граници на датата“. Ето защо получаваме резултатите, които правим в предишните примери. Лесно е да се предположи, че DATEDIFF() използва изминалото време за своите изчисления, но не го прави. Използва броя на прекосените граници на датата.

В първия пример датите не преминават граници на част от годината. Годината на първата дата беше точно същата като годината на втората дата. Не бяха прекрачени граници.

Във втория пример имахме обратния сценарий. Датите прекосиха всяка граница на датата поне веднъж (100 пъти за наносекунди).

Пример 3 – различен резултат за седмица

Сега, нека се преструваме, че е изминала цяла година. И тук сме точно една година по-късно със стойностите за дата/час, с изключение на това, че стойностите на годината са се увеличили с една.

Трябва да постигнем същите резултати, нали?

DECLARE @startdate datetime2 ='2017-12-31 23:59:59.9999999', @enddate datetime2 ='2018-01-01 00:00:00.0000000';ИЗБЕРЕТЕ @stardatdate (година, година) , DATEDIFF(тримесечие, @startdate, @enddate) Тримесечие, DATEDIFF(месец, @startdate, @enddate) Месец, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Ден, DATEDIFF (седмица, @startdate, @enddate) Седмица, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(минута, @startdate, @enddate) Минута, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(милисекунда , @startdate, @enddate) Милисекунда, DATEDIFF(микросекунда, @startdate, @enddate) Микросекунда, DATEDIFF(наносекунда, @startdate, @enddate) Наносекунда;

Резултати:

<пред>Година | 1 тримесечие | 1 месец | 1DOY | 1 ден | 1 седмица | 0 час | 1 минута | 1 секунда | 1 милисекунда | 1 микросекунда | 1 наносекунда | 100

Грешно.

Повечето от тях са еднакви, но този път седмицата върна 0 .

А?

Това се случи, защото въведените дати имат една и съща календарна седмица стойности. Така се случи, че избраните дати например 2 имат различни стойности на календарната седмица.

За да бъдем по-конкретни, пример 2 премина границите на седмичната част от „2016-12-31“ до „2017-01-01“. Това е така, защото последната седмица на 2016 г. завърши на 31.12.2016 г., а първата седмица на 2017 г. започна на 01.01.2017 г. (неделя).

Но в пример 3 първата седмица на 2018 г. всъщност започна на нашата начална дата 31.12.2017 г. (неделя). Нашата крайна дата, която е следващият ден, падна в рамките на същата седмица. Следователно не бяха прекрачени границите на седмичната част.

Това очевидно предполага, че неделята е първият ден от всяка седмица. Както се оказва, DATEDIFF() функция прави приемем, че неделя е първият ден от седмицата. Той дори игнорира вашия SET DATEFIRST настройка (тази настройка ви позволява изрично да посочите кой ден се счита за първи ден от седмицата). Разсъжденията на Microsoft за игнорирането на SET DATEFIRST е, че гарантира DATEDIFF() функцията е детерминирана. Ето решение, ако това е проблем за вас.

Така че накратко, вашите резултати могат да изглеждат „грешни“ за всяка част от датата в зависимост от датите/часовете. Резултатите ви могат да изглеждат много погрешни, когато използвате седмичната част. И биха могли да изглеждат още по-грешни, ако използвате SET DATEFIRST стойност, различна от 7 (за неделя) и очаквате DATEDIFF() да уважавам това.

Но резултатите не са грешни и не е грешка. Това е по-скоро "разбирам" за тези, които не знаят как всъщност работи функцията.

Всички тези проблеми се отнасят и за DATEDIFF_BIG() функция. Работи по същия начин като DATEDIFF() с изключението, че връща резултата като знак bigint (за разлика от int за DATEDIFF() ).

Пример 4 – Резултатите зависят от типа данни

Можете също да получите неочаквани резултати поради типа данни, който използвате за въведените си дати. Резултатите често ще се различават в зависимост от типа данни на въведените дати. Но не можете да обвинявате DATEDIFF() за това, тъй като се дължи единствено на възможностите и ограниченията на различните типове данни. Не можете да очаквате да получите резултати с висока точност от ниска прецизна входна стойност.

Например всеки път, когато началната или крайната дата има smalldatetime стойност, секундите и милисекундите винаги ще връщат 0. Това е така, защото smalldatetime типът данни е точен само до минутата.

Ето какво се случва, ако превключим пример 2 да използва smalldatetime вместо datetime2 :

DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';ИЗБЕРЕТЕ DATEDIFF(година, @startdate, @enddate) Година , DATEDIFF(тримесечие, @startdate, @enddate) Тримесечие, DATEDIFF(месец, @startdate, @enddate) Месец, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Ден, DATEDIFF (седмица, @startdate, @enddate) Седмица, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(минута, @startdate, @enddate) Минута, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(милисекунда , @startdate, @enddate) Милисекунда, DATEDIFF(микросекунда, @startdate, @enddate) Микросекунда, DATEDIFF(наносекунда, @startdate, @enddate) Наносекунда;

Резултат:

<пред>Година | 0Квартал | 0 месеца | 0DOY | 0Ден | 0 седмица | 0 час | 0 минути | 0 секунди | 0 милисекунда | 0 микросекунда | 0 наносекунда | 0

Причината, поради която всички те са нулеви, е, че и двете въведени дати всъщност са идентични:

DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';ИЗБЕРЕТЕ @startdate 'Начална дата', @enddate 'Край Дата';

Резултат:

+---------------------+---------------------+| Начална дата | Крайна дата ||---------------------+---------------------|| 01.01.2017 00:00:00 | 2017-01-01 00:00:00 |+---------------------+---------------- -----+

Ограниченията на smalldatetime типът данни накара секундите да бъдат закръглени нагоре, което след това предизвика поток върху ефекта и всичко се закръгли нагоре. Дори ако не се окажете с еднакви входни стойности, пак може да получите неочакван резултат поради типа данни, които не осигуряват необходимата прецизност.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. YEAR() Примери в SQL Server (T-SQL)

  2. Как да вмъкна байт[] в колона VARBINARY на SQL сървър

  3. Разлика между база данни и схема

  4. Деактивирайте SA акаунта в SQL Server (пример за T-SQL)

  5. Нулиране на началната стойност на самоличността след изтриване на записи в SQL Server