sql >> Databáze >  >> RDS >> Sqlserver

DATEDIFF() vrací nesprávné výsledky na serveru SQL? Přečti si tohle.

Pokud při použití DATEDIFF() dostáváte opravdu divné výsledky funkce v SQL Server a jste přesvědčeni, že funkce obsahuje chybu, zatím si netrhejte vlasy. Pravděpodobně to není chyba.

Existují scénáře, kdy výsledky vytvořené touto funkcí mohou být docela šílené. A pokud nerozumíte tomu, jak funkce skutečně funguje, výsledky budou vypadat úplně špatně.

Doufejme, že tento článek může pomoci objasnit, jak DATEDIFF() funkce je navržena tak, aby fungovala, a poskytuje několik příkladů scénářů, kdy vaše výsledky nemusí být takové, jaké byste očekávali.

Příklad 1 – 365 dní není vždy rok

Otázka: Kdy je 365 dní ne rok?

Odpověď: Při použití DATEDIFF() samozřejmě!

Zde je příklad, kdy používám DATEDIFF() vrátit počet dní mezi dvěma daty a poté počet let mezi stejnými dvěma daty.

DECLARE 
  @startdate datetime2 = '2016-01-01  00:00:00.0000000', 
  @enddate datetime2 = '2016-12-31 23:59:59.9999999';
SELECT 
  DATEDIFF(day, @startdate, @enddate) Days,
  DATEDIFF(year, @startdate, @enddate) Years;

Výsledek:

+--------+---------+
| Days   | Years   |
|--------+---------|
| 365    | 0       |
+--------+---------+

Pokud si myslíte, že tento výsledek je nesprávný, a to DATEDIFF() zjevně má chybu, čtěte dál – ne všechno je tak, jak se zdá.

Věřte nebo ne, toto je vlastně očekávaný výsledek. Tento výsledek je přesně v souladu se způsobem DATEDIFF() je navržen tak, aby fungoval.

Příklad 2 – 100 nanosekund =1 rok?

Vezměme to jinak.

DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2017-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Výsledky (zobrazené s vertikálním výstupem):

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 1
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Mezi těmito dvěma daty/časy je rozdíl pouze sto nanosekund (0,0000001 sekundy), přesto dostáváme přesně stejný výsledek pro každou část data, kromě nanosekund.

Jak se to může stát? Jak může být rozdíl 1 mikrosekunda a rozdíl 1 rok oba současně? Nemluvě o všech datech mezi tím?

Může se to zdát bláznivé, ale ani to není chyba. Tyto výsledky jsou přesně v souladu se způsobem DATEDIFF() má fungovat.

A aby to bylo ještě více matoucí, mohli bychom získat různé výsledky v závislosti na typu dat. Ale k tomu se brzy dostaneme. Nejprve se podívejme, jak funguje DATEDIFF() funkce skutečně funguje.

Aktuální definice DATEDIFF()

Důvodem, proč získáváme výsledky, které děláme, je DATEDIFF() funkce je definována následovně:

Tato funkce vrací počet (jako hodnotu celého čísla se znaménkem) zadaných hranic části data překročených mezi zadaným počátečním datem a datum ukončení .

Věnujte zvláštní pozornost slovům „překročeny hranice datové části“. Proto dostáváme výsledky jako v předchozích příkladech. Je snadné předpokládat, že DATEDIFF() používá pro své výpočty uplynulý čas, ale ne. Používá počet překročených hranic datové části.

V prvním příkladu data nepřekročila žádné roční hranice. Rok prvního rande byl přesně stejný jako rok druhého rande. Nebyly překročeny žádné hranice.

Ve druhém příkladu jsme měli opačný scénář. Data překročila hranici každé části data alespoň jednou (100krát pro nanosekundy).

Příklad 3 – Jiný výsledek za týden

Nyní předstírejme, že uplynul celý rok. A tady jsme přesně o rok později s hodnotami data/času, kromě toho, že se hodnoty roku zvýšily o jednu.

Měli bychom mít stejné výsledky, ne?

DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2018-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Výsledky:

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 0
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Špatně.

Většina z nich je stejná, ale tentokrát týden vrátil 0 .

Co?

Stalo se to proto, že zadaná data mají stejný kalendářní týden hodnoty. Náhodou se stalo, že data vybraná pro příklad 2 měla různé hodnoty kalendářního týdne.

Abychom byli konkrétnější, příklad 2 překročil hranice týdenní části od „2016-12-31“ do „2017-01-01“. Důvodem je, že poslední týden roku 2016 skončil 31. 12. 2016 a první týden roku 2017 začal 1. 1. 2017 (neděle).

Ale v příkladu 3 první týden roku 2018 ve skutečnosti začal naším počátečním datem 2017-12-31 (neděle). Naše konečné datum, které bylo následující den, spadalo do stejného týdne. Nebyly proto překročeny žádné týdenní hranice.

To samozřejmě předpokládá, že neděle je prvním dnem každého týdne. Jak se ukázalo, DATEDIFF() funkce dělá předpokládejme, že neděle je prvním dnem týdne. Ignoruje dokonce vaše SET DATEFIRST nastavení (toto nastavení umožňuje explicitně určit, který den je považován za první den v týdnu). Důvody společnosti Microsoft pro ignorování SET DATEFIRST je, že zajišťuje DATEDIFF() funkce je deterministická. Zde je řešení, pokud je to pro vás problém.

Stručně řečeno, vaše výsledky mohou vypadat „špatně“ pro jakoukoli část data v závislosti na datech/časech. Při použití týdenní části mohou vaše výsledky vypadat extra špatně. A mohou vypadat ještě špatně, pokud použijete SET DATEFIRST hodnotu jinou než 7 (pro neděli) a očekáváte DATEDIFF() ctít to.

Ale výsledky nejsou špatné a není to chyba. Je to spíše „problém“ pro ty, kteří nevědí, jak funkce skutečně funguje.

Všechny tyto chyby se vztahují také na DATEDIFF_BIG() funkce. Funguje stejně jako DATEDIFF() s tou výjimkou, že vrátí výsledek jako bigint se znaménkem (na rozdíl od int pro DATEDIFF() ).

Příklad 4 – Výsledky závisí na typu dat

Můžete také získat neočekávané výsledky kvůli datovému typu, který používáte pro svá vstupní data. Výsledky se budou často lišit v závislosti na typu dat zadaných dat. Ale nemůžete vinit DATEDIFF() protože je to čistě kvůli možnostem a omezením různých typů dat. Nemůžete očekávat, že získáte vysoce přesné výsledky z nízké vstupní hodnoty.

Například vždy, když má počáteční nebo koncové datum hodnotu smalldatetime hodnotu, sekundy a milisekundy vždy vrátí 0. Důvodem je, že smalldatetime datový typ je pouze na minutu přesný.

Co se stane, když příklad 2 přepneme na smalldatetime místo datetime2 :

DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
  @enddate smalldatetime = '2017-01-01 00:00:00';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Výsledek:

Year        | 0
Quarter     | 0
Month       | 0
DOY         | 0
Day         | 0
Week        | 0
Hour        | 0
Minute      | 0
Second      | 0
Millisecond | 0
Microsecond | 0
Nanosecond  | 0

Důvod, proč jsou všechny nulové, je ten, že obě vstupní data jsou ve skutečnosti totožná:

DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
  @enddate smalldatetime = '2017-01-01 00:00:00';
SELECT  
  @startdate 'Start Date',   
  @enddate 'End Date';

Výsledek:

+---------------------+---------------------+
| Start Date          | End Date            |
|---------------------+---------------------|
| 2017-01-01 00:00:00 | 2017-01-01 00:00:00 |
+---------------------+---------------------+

Omezení smalldatetime datový typ způsobil zaokrouhlení sekund nahoru, což následně způsobilo tok na efekt a vše se zaokrouhlilo nahoru. I když neskončíte se stejnými vstupními hodnotami, stále můžete získat neočekávaný výsledek, protože datový typ neposkytuje požadovanou přesnost.


  1. Funkce COALESCE() v Oracle

  2. Jak kopírovat data z jedné tabulky do druhé v SQL

  3. Vyhledejte všechny výskyty řetězce v databázi mysql

  4. SELECT FOR XML AUTO a vrátit datové typy