Bir datetime değerinin (SQL Server) zaman bölümünü nasıl kaldırabilirim?


85

İşte kullandığım şey:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

Daha iyi ve daha zarif bir yol olabileceğini düşünüyorum.

Gereksinimler:

  • Mümkün olduğunca hızlı olmalı (ne kadar az döküm, o kadar iyi).
  • Nihai sonuç datetimebir dize değil, bir tür olmalıdır .

Yanıtlar:


116

SQL Server 2008 ve üstü

SQL Server 2008 ve sonrasında, elbette en hızlı yol Convert(date, @date). Bu, gerekirse a'ya datetimeveya datetime2gerekirse geri alınabilir .

SQL Server 2005 ve Daha Eski İçinde Gerçekten En İyi Olan Nedir?

SQL Server'da bir tarihten itibaren saati kısaltmanın en hızlı olanı hakkında tutarsız iddialar gördüm ve hatta bazıları test yaptıklarını söyledi, ancak benim deneyimim farklıydı. Şimdi biraz daha sıkı testler yapalım ve herkesin senaryoya sahip olmasına izin verelim, böylece herhangi bir hata yaparsam insanlar beni düzeltebilir.

Kayan Dönüşümler Doğru Değil

İlk olarak, doğru şekilde dönüştürülmediği datetimeiçin dönüşümden uzak floatdururdum. Zaman kaldırma işini doğru bir şekilde yapmaktan kurtulabilirsiniz, ancak bunu kullanmanın kötü bir fikir olduğunu düşünüyorum çünkü geliştiricilere bunun güvenli bir işlem olduğunu ve olmadığını dolaylı olarak iletiyor . Bir göz at:

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

Bu, insanlara kodumuzda veya çevrimiçi örneklerimizde öğretmemiz gereken bir şey değildir.

Ayrıca, en hızlı yol bile değil!

İspat - Performans Testi

Farklı yöntemlerin gerçekte nasıl yığıldığını görmek için kendi kendinize bazı testler yapmak istiyorsanız, testleri daha aşağıda çalıştırmak için bu kurulum betiğine ihtiyacınız olacak:

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

Lütfen bunun veritabanınızda 427.57 MB'lık bir tablo oluşturacağını ve 15-30 dakika gibi bir süre alacağını unutmayın. Veritabanınız küçükse ve% 10 büyümeye ayarlanmışsa, ilk önce yeterince büyük olmanıza göre daha uzun sürecektir.

Şimdi gerçek performans testi komut dosyası için. Lütfen satırları istemciye geri döndürmemenin amaçlı olduğunu unutmayın, çünkü bu 26 milyon satırda çılgınca pahalıdır ve yöntemler arasındaki performans farklılıklarını gizleyecektir.

Performans Sonuçları

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;

Bazı Rambling Analizi

Bununla ilgili bazı notlar. Her şeyden önce, sadece bir GROUP BY veya bir karşılaştırma yapıyorsanız, geri dönmeye gerek yoktur datetime. Dolayısıyla, görüntüleme amacıyla nihai değere ihtiyacınız olmadığı sürece, bundan kaçınarak bazı CPU'lardan tasarruf edebilirsiniz. Hatta dönüştürülmemiş değeri GROUP BY bile yapabilir ve dönüşümü yalnızca SELECT yan tümcesine koyabilirsiniz:

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

Ayrıca, sayısal dönüşümlerin geri dönüştürülmesinin nasıl biraz daha fazla zaman aldığını datetime, ancak varchardönüşümün neredeyse iki katına çıktığını görün. Bu, CPU'nun sorgulardaki tarih hesaplamasına ayrılan kısmını ortaya çıkarır. CPU kullanımının tarih hesaplamasını içermeyen kısımları vardır ve bu, yukarıdaki sorgularda 19875 ms'ye yakın bir şey gibi görünmektedir. Daha sonra dönüştürme ek bir miktar alır, bu nedenle iki dönüştürme varsa, bu miktar yaklaşık iki kat kullanılır.

Daha muayene ile karşılaştırıldığında ortaya koymaktadır Convert(, 112), Convert(, 101)(daha uzun kullandığından sorgu bazı ek işlemci gideri bulunmaktadır varchar?) İkinci dönüşüm geri çünkü datebaşlangıçtaki dönüşüm olduğu kadar mal olmaz varcharile ancak Convert(, 112)bu kadar yakınsa aynı 20000 ms CPU temel maliyeti.

İşte yukarıdaki analiz için kullandığım CPU zamanıyla ilgili hesaplamalar:

     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 , geri dönüş için CPU zamanıdır datetime.

  • single , alternatif veri türüne (zaman bölümünü kaldırmanın yan etkisine sahip olan) tek bir dönüşüm için CPU zamanıdır.

  • taban çıkarılarak hesaplanmasıdır singleiki çağrıları arasındaki fark: single - (round - single). Bu, bu veri türüne ve bu veri türünden dönüşümü varsayan ve datetimeher iki yönde de yaklaşık olarak aynı olan bir tahmin rakamıdır . Görünüşe göre bu varsayım mükemmel değil ama yakın çünkü değerlerin hepsi sadece bir istisna dışında 20000 ms'ye yakın.

Bir başka ilginç şey ise, temel maliyetin neredeyse tek Convert(date)yönteme eşit olmasıdır (sunucu, datetimeveri türünün ilk dört baytından tam sayı gün bölümünü dahili olarak çıkarabildiğinden, neredeyse 0 maliyet olmalıdır ).

Sonuç

Yani tek yönlü varchardönüştürme yönteminin yaklaşık 1.8 μs ve tek yönlü DateDiffyöntemin yaklaşık 0.18 μs sürmesi gibi görünüyor. Bunu, 25.920.000 satır için toplam 18458 ms'lik testimdeki en muhafazakar "temel CPU" süresine dayandırıyorum, yani 23218 ms / 25920000 = 0.18 μs. Görünen 10x iyileştirme çok gibi görünüyor, ancak yüz binlerce satırla uğraşana kadar (617 bin satır = 1 saniye tasarruf) açıkçası oldukça küçük.

Bu küçük mutlak gelişme göz önüne alındığında bile, bence DateAddyöntem kazanır çünkü performans ve netliğin en iyi birleşimidir. "Sihirli sayı" gerektiren cevap bir 0.50000004gün birini ısıracak (beş sıfır veya altı ???), ayrıca anlaşılması daha zor.

ek Notlar

Ne zaman ben değişime gidiyorum biraz zaman alır 0.50000004için '12:00:00.003've öyle bakın. Aynı datetimedeğere dönüştürülür ve hatırlamayı çok daha kolay buluyorum.

İlgilenenler için, yukarıdaki testler @@ Version'un aşağıdakileri döndürdüğü bir sunucuda yapılmıştır:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) Temmuz 9 2008 14:43:34 Telif Hakkı (c) 1988-2008 Windows NT 5.2 üzerinde Microsoft Corporation Standard Edition (Derleme 3790: Service Pack 2)


1
+1 Bu arada, bunu SQL Server'ın hangi sürümünü test ettiniz?
Martin Smith

1
Görünüşe göre masanızda tek ve geriye doğru yuvarlak var . Ayrıca, charyerine kullanırsanız zaman açısından herhangi bir fark olur varcharmu?
Gabe

1
@Gabe teşekkürler, düzeltildi. Char, varchar ile tamamen aynı görünüyor.
ErikE

Oracle'da select round(sysdate) from dualSql Server'da buna kesinlikle ihtiyacımız var.
Denis Valeev

3
@Roman SQL Server 2008 ve üstü ile çalışıyorsanız, evet, dateyukarıdaki testlerimde gösterildiği gibi veri türüne dönüştürmek en hızlısıdır.
ErikE

30

SQL Server 2008, yeni bir tarih veri türüne sahiptir ve bu, bu sorunu aşağıdakiler için basitleştirir:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

1
Yıl olarak 2018 yerine yanlışlıkla 0218 girmiştim DATEADD(DATEDIFF())ve zaman bölümünü kesme yöntemi bir istisna yaratıyor. Ben dökme zaman için sonuç geri datetime2yönteminiz güzel çalışıyorselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
Bernhard DOBLER

18

DATETIME Hesaplamalarında Itzik Ben-Gan , Bölüm 1 (SQL Server Dergisi, Şubat 2007), böyle bir dönüşümü gerçekleştirmenin üç yöntemini gösterir ( en yavaştan en hızlıya ; ikinci ve üçüncü yöntem arasındaki fark küçüktür):

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)

Tekniğiniz ( float için döküm ), derginin Nisan sayısında bir okuyucu tarafından önerilmektedir. Ona göre, yukarıda sunulan ikinci teknikle karşılaştırılabilir performansa sahiptir.


1
Benim fikrime göre, yüzdürmek en iyisi değil. Lütfen
cevabıma

1
@Emtucifor 3. yöntemin 0.50000004 değeri nedeniyle çok belirsiz olduğuna katılıyorum , ancak bu en hızlısı ve testleriniz bunu doğruluyor . Böylelikle ihtiyacı mümkün olan en hızlı şekilde karşılar.
Marek Grzenkowicz

1
@Emtucifor Ayrıca, bağladığım makale 0.50000004 değeri hakkında şöyle diyor : Bu ifade kısa (ve kısaca göstereceğim gibi verimli) olmasına rağmen, bundan rahatsız olduğumu söylemeliyim . Tam olarak neden diye parmağımı koyabileceğimden emin değilim - belki de çok teknik olduğu için ve tarih saatle ilgili mantığı göremiyorsunuz.
Marek Grzenkowicz

2
Bu yöntemi kullanacaksak, SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)bunun yerine benim için bir anlamı olduğu ve hatırlaması çok daha kolay olduğu için tercih ederim .
ErikE

6
Bu SQL 2008'de artık en hızlı: Convert(date, GetDate()).
ErikE

12

En azından MS SQL Server 2005'te CAST- FLOOR- CASTzaten en uygun yol gibi görünüyor.

Gördüğüm bazı diğer çözümlerde olduğu gibi Select Convert(varchar(11), getdate(),101), 10 kat daha yavaş bir dizi dönüşümü var .


1
Michael Stum'un önerdiği yöntemi ürünlerimizden birinde kullanıyoruz ve bu bir cazibe gibi çalışıyor.
Chris Roberts

3
Bu pek de optimum yol değil. Bakınız Cevabımı bu aynı sayfada.
ErikE

4

Deneyin lütfen:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]

1

SQL2005: dateadd yerine castlemeyi öneririm. Örneğin,

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

yaklaşık 10 ortalama% daha hızlı daha benim veri kümesi üzerinde

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

(ve küçük tarihe çevrim daha hızlıydı)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.