Jak usunąć część czasową wartości datetime (SQL Server)?

Oto czego używam:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)
Myślę, że może być lepszy i bardziej elegancki sposób.

Wymagania:

  • musi być jak najszybciej (im mniej rzutów, tym lepiej).
  • końcowy wynik musi być typem datetime, a nie łańcuchem znaków.
Author: Ellis, 2008-08-06

6 answers

SQL Server 2008 i up

W SQL Server 2008 i w górę, oczywiście najszybszym sposobem jest Convert(date, @date). W razie potrzeby można to cofnąć do datetime lub datetime2.

Co jest naprawdę najlepsze w SQL Server 2005 i starszych?

Widziałem niespójne twierdzenia o tym, co jest najszybsze do skrócenia czasu od daty w SQL Server, a niektórzy ludzie nawet powiedzieli, że testowali, ale moje doświadczenie było inne. Więc zróbmy bardziej rygorystyczne testy i niech każdy ma scenariusz, więc jeśli popełnię jakieś błędy, ludzie mogą mnie poprawić.

Konwersje Float Nie Są Dokładne

Po pierwsze, trzymałbym się z dala od konwersji datetime na float, ponieważ nie konwertuje się poprawnie. Możesz ujść na sucho z dokładnym usuwaniem czasu, ale myślę, że używanie go jest złym pomysłem, ponieważ w domyśle komunikuje programistom, że jest to bezpieczna operacja, a nie jest. Zobacz też:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

To nie jest coś, czego powinniśmy uczyć ludzi w naszym kodzie lub w naszych przykładach online.

Poza tym, nie jest to nawet najszybsza droga!

Proof-Performance Testing

Jeśli chcesz wykonać kilka testów samodzielnie, aby zobaczyć, jak różne metody naprawdę się układają, będziesz potrzebował tego skryptu instalacyjnego, aby uruchomić testy dalej w dół:

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

Należy pamiętać, że tworzy to tabelę 427,57 MB w bazie danych i zajmie około 15-30 minut. Jeśli twoja baza danych jest mała i ustawiona na 10% wzrost zajmie to dłużej niż w przypadku, gdy najpierw będziesz wystarczająco duży.

Teraz skrypt testujący wydajność. Należy pamiętać, że celowe jest nie zwracanie wierszy z powrotem do klienta, ponieważ jest to szalenie kosztowne w przypadku 26 milionów wierszy i ukryłoby różnice wydajności między metodami.

Wyniki Wydajności

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

Niektóre Bełkotliwe Analizy

Kilka uwag na ten temat. Po pierwsze, jeśli tylko wykonując grupę przez lub porównanie, nie ma potrzeby konwersji z powrotem do datetime. Możesz więc zaoszczędzić trochę procesora, unikając tego, chyba że potrzebujesz ostatecznej wartości do celów wyświetlania. Można nawet pogrupować według wartości niekonwertowanej i umieścić konwersję tylko w klauzuli SELECT:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

Zobacz też, jak konwersje liczbowe zajmują nieco więcej czasu, aby przekształcić je z powrotem na datetime, ale konwersja varchar prawie podwaja się? Ujawnia to część procesora, która jest poświęcona obliczeniu daty w zapytaniach. Istnieją części użycia procesora, które nie wymagają obliczania daty, a to wydaje się być czymś zbliżonym do 19875 ms w powyższych zapytaniach. Następnie konwersja pobiera dodatkową kwotę, więc jeśli są dwie konwersje, kwota ta jest zużywana około dwa razy.

Więcej badań ujawnia, że w porównaniu do Convert(, 112), zapytanie Convert(, 101) ma jakiś dodatkowy wydatek CPU (ponieważ używa dłuższego varchar?), ponieważ druga konwersja z powrotem do date nie kosztuje tyle jako początkową konwersję na varchar, ale z Convert(, 112) jest bliżej tego samego kosztu bazowego procesora 20000 ms.

Oto te obliczenia na czas procesora, które użyłem do powyższej analizy:

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • round to czas procesora na podróż powrotną do datetime.

  • single to czas procesora dla pojedynczej konwersji na alternatywny typ danych (ten, który ma efekt uboczny usunięcia czasu porcja).

  • baza {[86] } jest obliczeniem odejmowania od single różnicy między dwoma wywołaniami: single - (round - single). Jest to liczba, która zakłada konwersję do i z tego typu danych i datetime jest w przybliżeniu taka sama w obu kierunkach. Wydaje się, że to założenie nie jest doskonałe, ale jest bliskie, ponieważ wszystkie wartości są bliskie 20000 ms z tylko jednym wyjątkiem.

[[32]}jeszcze jedną ciekawą rzeczą jest to, że koszt bazowy jest prawie jest równa pojedynczej metodzie Convert(date) (która musi wynosić prawie 0, ponieważ serwer może wewnętrznie wyodrębnić całkowitą część dnia z pierwszych czterech bajtów typu danych datetime).

Wniosek

Wygląda więc na to, że konwersja jednokierunkowa varchar zajmuje około 1,8 µs, A jednokierunkowa DateDiff około 0,18 µs. Bazuję na najbardziej konserwatywnym czasie "bazowego procesora" w moich testach 18458 ms dla 25,920,000 wierszy, czyli 23218 ms / 25920000 = 0,18 µs. Pozorna poprawa 10x wydaje się dużo, ale jest szczerze mówiąc dość mała, dopóki nie masz do czynienia z setkami tysięcy wierszy (wiersze 617k = oszczędności 1 sekundy).

Nawet biorąc pod uwagę tę niewielką absolutną poprawę, moim zdaniem metoda DateAdd wygrywa, ponieważ jest to najlepsze połączenie wydajności i przejrzystości. Odpowiedź, która wymaga "magicznej liczby" 0.50000004, kiedyś kogoś ugryzie(pięć zer czy sześć???), dodatkowo trudniej jest Rozumiem.

Uwagi Dodatkowe

Kiedy będę miał trochę czasu, zmienię 0.50000004 na {[30] } i zobaczę, jak to będzie. Jest przekonwertowana na tę samą wartość datetime i uważam, że jest o wiele łatwiejsza do zapamiętania.

Dla zainteresowanych powyższe testy zostały uruchomione na serwerze, na którym @ @ Version zwraca:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 09 lipca 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Standard Wersja na Windows NT 5.2 (Build 3790: Service Pack 2)

 106
Author: ErikE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-17 18:18:06

SQL Server 2008 ma nowy date typ danych i upraszcza ten problem do:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)
 28
Author: Marek Grzenkowicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-13 16:57:04

Itzik Ben-Gan w DateTime Calculations, Part 1 (SQL Server Magazine, luty 2007) pokazuje trzy metody wykonywania takiej konwersji (najwolniejsza do najszybszej; różnica między drugą i trzecią metodą jest niewielka):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

Twoja technika (rzucanie do float ) została zasugerowana przez czytelnika w kwietniowym numerze magazynu. Według niego ma ona osiągi porównywalne z osiągami drugiej techniki przedstawionej powyżej.

 16
Author: Marek Grzenkowicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-24 08:08:00

Twój CAST-FLOOR-CAST już wydaje się być optymalnym sposobem, przynajmniej na MS SQL Server 2005.

Inne rozwiązania, które widziałem, mają konwersję ciągu, Jak Select Convert(varchar(11), getdate(),101) w nich, która jest wolniejsza o współczynnik 10.

 11
Author: Michael Stum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-04 10:02:28

Proszę spróbować:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
 3
Author: srihari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-05 13:18:14

Sql2005: polecam cast zamiast dateadd. Na przykład,

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

Średnio około 10% szybciej na moim zbiorze danych, niż

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(i wrzucenie do smalldatetime było jeszcze szybsze)

 0
Author: user4217069,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-05 04:26:40