Sorgu arama tarih saatim neden eşleşmiyor?


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Ancak sonuç, bugün_tarihi olarak yayınlanan bir kayıt içeriyor: 2015-07-28. Veritabanı sunucum ülkemde değil. Sorun nedir ?

Yanıtlar:


16

datetimeVeri türünü kullandığınızdan , sql sunucusunun datetime verilerini nasıl yuvarladığını anlamanız gerekir.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   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 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

resim açıklamasını buraya girin

Aşağıdaki sorguyu kullanarak, DATETIMEveri türünü kullandığınızda sql sunucusunun yaptığı yuvarlama sorununu kolayca görebilirsiniz .

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

resim açıklamasını buraya girin Büyütmek için tıklayın

DATETIME2SQL Server 2008'den beri var, bunun yerine kullanmaya başlayın DATETIME. Durumunuza için kullanabilirsiniz datetime2ile 3 ondalık sayılar hassasiyeti örn datetime2(3).

Kullanmanın Yararları datetime2:

  • Vs zaman bileşeni için 7 ondalık basamağa kadar Destekler datetimesadece 3 ondalık basamak destekleyen .. ve dolayısıyla varsayılan tarafından beri yuvarlama sorunu bakınız datetimemermi yakın .003 secondsartışlarla birlikte .000, .003ya da .007saniyeler.
  • datetime2çok daha hassastır datetimeve datetime2size kontrol DATEve TIMEaksine datetime.

Referans :


1
gives you control of DATE and TIME as opposed to datetime.Bu ne anlama geliyor?
nurettin

Yeniden. DateTime2vs. kullanarak DateTime: a. İçin - - engin - çoğunluğun - of - gerçek - Dünya - kullanım - vakalar faydaları DateTime2Kadar <maliyetleri. Bkz . Stackoverflow.com/questions/1334143/… b. Buradaki temel sorun bu değil. Bir sonraki yoruma bakın.
Tom

Buradaki kök problem (en kıdemli geliştiricilerin kabul edeceği gibi), bir tarih-zaman aralığı karşılaştırmasının kapsayıcı bitiş tarihi-saatinde yetersiz hassasiyet değil, kapsayıcı (münhasır) bir dönemin kullanılmasıdır. Bu, Pi ile eşitliği kontrol etmek gibi, her zaman #s'den birinin> veya <hassasiyete sahip olma olasılığı vardır (yani datetime3, 70 (vs 7) basamaklar hassasiyet eklenirse?). En iyi uygulama hassas, madde yani <değil bir değeri kullanmaktır başlangıç sonraki ikinci, dakika, saat veya gün vs. <= ait sonuna öncesinde ikinci, dakika, saat veya gün.
Tom

18

Bazılarının yorumlarda ve sorunuzun diğer cevaplarında bahsettiği gibi, asıl sorun SQL Server tarafından 2015-07-27 23:59:59.999yuvarlanıyor 2015-07-28 00:00:00.000. Dokümantasyon Başına için DATETIME:

Zaman aralığı - 00:00:00 - 23: 59: 59.997

Zaman aralığının asla olmayacağını unutmayın .999. Belgelerde ayrıca SQL Server'ın en az anlamlı basamak için kullandığı yuvarlama kurallarını belirtir.

Yuvarlama kurallarını gösteren tablo

En az anlamlı basamak sadece üç potansiyel değerden birine sahip olabilir: "0", "3" veya "7".

Bunun için kullanabileceğiniz birkaç çözüm / geçici çözüm vardır.

-- 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

Yukarıda sunduğum beş seçenekten 1 ve 3 numaralı seçenekleri tek geçerli seçenekler olarak görüyorum. Niyetinizi açıkça ifade ederler ve veri türlerini güncellerseniz kırılmazlar. SQL Server 2008 veya daha yenisini kullanıyorsanız seçenek 3'ün tercih ettiğiniz yaklaşım olduğunu düşünüyorum. Bu, özellikle veri türünü sütununuz için DATETIMEbir DATEveri türüne dönüştürmekten başka bir şey yapamazsanız geçerlidir posted_date.

Seçenek 3 ile ilgili olarak, bazı konular hakkında çok iyi bir açıklama burada bulunabilir: Bugüne kadar yayınlamak anlaşılabilir, ancak iyi bir fikir mi?

Seçenek 2 ve 5'i sevmiyorum çünkü .997kesirli saniye, insanların "düzeltmek" isteyeceği başka bir sihirli sayı olacak. Bazı nedenlerden ötürü BETWEENyaygın olarak benimsenmemek için bu gönderiyi kontrol etmek isteyebilirsiniz .

Seçenek 4'ü sevmiyorum çünkü karşılaştırma amacıyla veri türlerini bir dizeye dönüştürmek bana kirli geliyor. SQL Server'da bundan kaçınmak için daha kalitatif bir sebep, bir dizin araması yapamayacağınız ve genellikle daha düşük performansla sonuçlanabilecek olan acizliği etkiler .

Tarih aralığı sorgularını işlemenin doğru ve yanlış yolu hakkında daha fazla bilgi için Aaron Bertrand tarafından gönderilen bu gönderiye göz atın .

Ayrılık size orijinal sorgu tutmak mümkün olacaktır ve size sizin değiştirirseniz istediğiniz gibi davranırdı posted_datebir sütunu DATETIMEa DATETIME2(3). Bu, sunucuda depolama alanından tasarruf etmenizi sağlar, aynı hassasiyette size daha fazla doğruluk sağlar, daha fazla standartla uyumlu / taşınabilir olur ve gelecekte ihtiyaçlarınız değişirse doğruluk / hassasiyeti kolayca ayarlamanıza olanak tanır. Ancak, bu yalnızca SQL Server 2008 veya daha yenisini kullanıyorsanız bir seçenektir.

Biraz trivia olarak, 1/300ikinci bir doğruluk ile bu StackOverflow cevap başınaDATETIME UNIX bir tutma gibi görünüyor . Paylaşılan bir mirasa sahip olan Sybase , kendi ve veri türlerinde ikinci bir doğruluğa benzer , ancak en az önemli basamakları "0", "3" ve "6" da farklı bir dokunuş. Bence ikinci ve / veya 3.33 ms doğruluk talihsiz bir mimari karardır çünkü SQL Server'ın veri tipindeki zaman için 4 baytlık blok 1 ms doğruluğu kolaylıkla destekleyebilirdi.1/300DATETIMETIME1/300DATETIME


Evet, ancak temel "temel sorun" seçenek 1'i kullanmıyor (ör . Geçmişin veya gelecekteki potansiyel veri türlerinin hassasiyetinin sonuçları etkileyebileceği herhangi bir kapsayıcı (hariç)) son değer kullanmak). Pi'ye eşitliği kontrol etmek gibi, bir # veya> hassasiyetine sahip olmak her zaman mümkündür (her ikisi de en düşük ortak hassasiyete önceden yuvarlanmadıkça). Ya datetime370 (7'ye karşı) basamak hassasiyeti eklenirse? En iyi uygulama, hassasiyetin önemli olmadığı bir değer kullanmaktır, yani <bir sonraki saniye, dakika, saat veya günün başlangıcı; <= önceki ikinci, dakika, saat veya günün sonu.
Tom

9

Örtük Dönüşüm

Posted_date veri türünün Datetime olduğunu varsayalım. Ancak, diğer taraftaki türün Datetime, Datetime2 veya Just Time olması önemli değildir, çünkü dize (Varchar) örtük olarak Datetime'a dönüştürülecektir.

Posted_date, Datetime2 (veya Time) olarak bildirildiğinde, geçerli bir Datetime2 değeri posted_date <= '2015-07-27 23:59:59.99999'olduğu için where cümlesi başarısız olur 23:59:59.99999, bu geçerli bir Datetime değeri değildir:

 Conversion failed when converting date and/or time from character string.

Tarih için Zaman Aralığı

Tarih saat aralığı 00:00:00 ila 23: 59: 59.997'dir. Bu nedenle 23: 59: 59.999 aralık dışındadır ve en yakın değere aşağı veya yukarı yuvarlanmalıdır.

doğruluk

Ayrıca Datetime değerleri .000, .003 veya .007 saniyelik artışlarla yuvarlanır. (ör. 000, 003, 007, 010, 013, 017, 020, ..., 997)

Bu 2015-07-27 23:59:59.999aralıktaki değerde durum böyle değildir : 2015-07-27 23:59:59.997ve 2015-07-28 0:00:00.000.

Bu aralık, her ikisi de .000, .003 veya .007 ile biten en yakın ve önceki seçeneklere karşılık gelir.

Yukarı veya aşağı yuvarlama ?

Bu daha yakın olduğu için 2015-07-28 0:00:00.000daha (+1 karşı -2) 2015-07-27 23:59:59.997: dizge yuvarlanır ve bu tarih saat değeri olur edilir 2015-07-28 0:00:00.000.

2015-07-27 23:59:59.998(Veya .995, .996, .997, .998) gibi bir üst sınırla, aşağı yuvarlanır 2015-07-27 23:59:59.997ve sorgunuz beklendiği gibi çalışırdı. Ancak bir çözüm değil, sadece şanslı bir değer olurdu.

Tarih2 veya Saat türleri

Datetime2 ve Zaman zaman aralıkları olan 00:00:00.0000000yoluyla 23:59:59.9999999100ns doğrulukla (7 haneli bir hassasiyetle kullanılan son rakam) ile yıkanmıştır.

Ancak bir Datetime (3) aralığı, Datetime aralığına benzemez:

  • tarih saat 0:0:00.000için23:59:59.997
  • Tarih2 0:0:00.000000000-23:59:59.999

Çözüm

Sonunda, ertesi günün altında, aşağıdaki tarihlerden daha düşük ya da günün son parçası olduğunu düşündüğünüze eşit olan tarihleri ​​aramak daha güvenlidir. Bunun nedeni, ertesi günün her zaman 0: 00: 00.000'da başladığını ancak farklı veri türlerinin günün sonunda aynı zamana sahip olmayabileceğini bilmenizdir:

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.000size doğru sonuçlar verecektir ve en iyi seçenektir
  • <= 2015-07-27 23:59:59.xxx olması gerektiğini düşündüğünüz şekilde yuvarlanmadığında beklenmedik değerler döndürebilir.
  • Dizin kullanımını sınırladığı için Tarihe dönüştürme ve işlev kullanımından kaçınılmalıdır

[Posted_date] değerini Datetime2 olarak değiştirmenin ve daha yüksek hassasiyetinin bu sorunu çözebileceğini düşünebiliriz, ancak dize hala Datetime'a dönüştürüldüğünden yardımcı olmaz. Ancak, bir kadro eklenirse cast(2015-07-27 23:59:59.999' as datetime2), bu iyi çalışır

Yayınla ve Dönüştür

Cast, 3 basamağa kadar bir değeri Datetime veya 9 basamağa kadar Datetime2 veya Time'a dönüştürebilir ve doğru hassasiyete yuvarlayabilir.

Datetime2 ve Time2 oyuncularının farklı sonuçlar verebileceği unutulmamalıdır:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) 2015-05-03 00: 00: 00.0000000 (999999949'dan büyük değerler için)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Bu, tarihin 0, 3 ve 7 artışlarla ilgili sorununu düzeltmekle birlikte, ertesi günün 1. nano saniyesinden önceki tarihleri ​​aramak her zaman daha iyidir (her zaman 0: 00: 00.000).

Kaynak MSDN: datetime (Transact-SQL)


6

Yuvarlanıyor

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 hepsi .997'ye kadar dökme / yuvarlak

Kullanmalı

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

veya

where cast(posted_date as date) = '2015-07-27'

Bu bağlantıdaki doğruluğa bakın
Her zaman .000, .003, .007 olarak bildirilir


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.