クエリ検索の日時が一致しないのはなぜですか?


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

しかし、結果には、今日のPosted_date:2015-07-28のレコードが含まれています。私のデータベースサーバーは私の国にありません。何が問題ですか ?

回答:


16

datetimeデータ型を使用しているため、sqlサーバーが日時データを丸める方法を理解する必要があります。

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

ここに画像の説明を入力してください

以下のクエリを使用すると、DATETIMEデータ型を使用するときにSQLサーバーが実行する丸めの問題を簡単に確認できます。

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

ここに画像の説明を入力してください 拡大するにはクリックしてください

DATETIME2はSQL Server 2008以来存在していたため、の代わりに使用を開始してくださいDATETIME。あなたの状況のた​​めに、あなたはで使用datetime2することができます 3小数点以下の精度などdatetime2(3)

使用の利点 datetime2

  • 時間コンポーネントの小数点以下7桁までをサポートするのに対し、小数点以下datetime3桁のみをサポートします。したがって、デフォルトdatetimeでは.003 seconds、の増分で最も近い値を丸めるため、丸めの問題が発生します.000.003または.007秒の。
  • datetime2はるかに正確であるdatetimedatetime2あなたがコントロールできますDATEし、TIMEは対照的にdatetime

参照 :


1
gives you control of DATE and TIME as opposed to datetime.どういう意味ですか?
nurettin

Re。DateTime2対を使用してDateTime:a。のために- 広大な-過半数 -の- 現実-世界 -使用-ケース、メリットDateTime2<コスト。参照:stackoverflow.com/questions/1334143/…b 。ここでは根本的な問題ではありません。次のコメントを参照してください。
トム

ここでの根本的な問題(ほとんどの上級開発者が同意するように)は、日時範囲の比較の包括的終了日時の精度が不十分ではなく、包括的(排他的)期間の使用です。これは、Piとの等価性をチェックするようなものです。#sのいずれかが>または<の精度を持っている可能性が常にあります(つまりdatetime3、70(vs. 7)桁の精度が追加された場合)。ベストプラクティスは、精度がない問題、すなわち<ない値を使用することで、スタート次の秒、分、1時間または1日対<=の終了前に、秒、分、1時間または1日のを。
トム

18

他のいくつかの人があなたの質問に対するコメントや他の回答で言及しているように、中心的な問題はSQL Serverによって2015-07-27 23:59:59.999丸められ2015-07-28 00:00:00.000ています。ドキュメントあたりのためにDATETIME

時間範囲-00:00:00から23:59:59.997

時間範囲はにならないことに注意してください.999。さらに下のドキュメントでは、SQL Serverが最下位桁に使用する丸め規則を指定しています。

丸め規則を示す表

最下位桁には、「0」、「3」、または「7」の3つの潜在的な値のいずれかしか指定できないことに注意してください。

これには、使用できる解決策/回避策がいくつかあります。

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

上記で提示した5つのオプションのうち、オプション1と3のみが実行可能なオプションであると考えます。これらは意図を明確に伝え、データ型を更新しても壊れません。SQL Server 2008以降を使用している場合、オプション3をお勧めします。データ型の使用からDATETIMEデータ型に変更できる場合は特にそうですDATEposted_dateの列。

オプション3については、いくつかの問題に関する非常に良い説明がここにあります。 これまでのキャストは検索可能ですが、それは良い考えですか?

.997秒の小数部は、人々が「修正」したい別の魔法の数になるため、オプション2と5は好きではありません。BETWEEN広く受け入れられないいくつかの理由により、この投稿をチェックアウトすることをお勧めします

比較4のためにデータ型を文字列に変換することは私にとって汚いので、オプション4は好きではありません。SQL Serverでこれを回避するより定性的な理由は、検索性に影響を与えることです。

日付範囲クエリを処理する正しい方法と間違った方法の詳細については、Aaron Bertrandによるこの投稿をご覧ください。

別れでは、あなたの元のクエリを維持することができるだろうと、それはあなたがあなたのご変更する場合は、必要に応じて振る舞うposted_dateから列をDATETIMEしますDATETIME2(3)。これにより、サーバー上のストレージスペースが節約され、同じ精度でより高い精度が得られ、より多くの標準に準拠/移植可能になり、将来ニーズが変化した場合に精度/精度を簡単に調整できます。ただし、これは、SQL Server 2008以降を使用している場合のオプションにすぎません。

ちょっとした雑学として、このStackOverflowの回答による1/300と、2番目の精度DATETIMEはUNIXからの引き継ぎのようです。共有の遺産を持つSybaseは、データ型データ型で2番目の精度に類似していますが、最下位桁は「0」、「3」、「6」でタッチが異なります。私の意見では、SQL Serverのデータ型の時間の4バイトブロックは1ミリ秒の精度を簡単にサポートできたため、 2番目および/または3.33ミリ秒の精度は不幸なアーキテクチャ上の決定です。1/300DATETIMETIME1/300DATETIME


はい、しかし、コア「問題の核心は、」(たとえば、使用してオプション1を使用していない任意の過去や将来の潜在的なデータ型の精度が結果に影響を与える可能性が包括的(対排他的な)範囲の終了値を)。これは、Piとの等価性をチェックするようなもので、精度が>または<である可能性が常にあります(両方が最低の共通精度に事前丸められていない限り)。どのような場合はdatetime370と精度の(対7)数字が追加しましたか?ベストプラクティスは、精度が重要でない値、つまり<次の秒、分、時間、または日の開始対<=前の秒、分、時間、または日の終了です。
トム

9

暗黙的な変換

Posted_dateデータ型はDatetimeであると想定しました。ただし、文字列(Varchar)は暗黙的にDatetimeに変換されるため、反対側の型がDatetime、Datetime2、または単なるTimeのいずれでもかまいません。

Posted_dateがDatetime2(またはTime)として宣言されているposted_date <= '2015-07-27 23:59:59.99999'場合、altough 23:59:59.99999は有効なDatetime2値であるため、where句は失敗しますが、これは有効なDatetime値ではありません。

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

日時の時間範囲

Datetimeの時間範囲は00:00:00〜23:59:59.997です。したがって、23:59:59.999は範囲外であり、最も近い値に切り上げまたは切り下げする必要があります。

正確さ

日付時刻の値は、.000、.003、または.007秒の増分で丸められます。(すなわち、000、003、007、010、013、017、020、...、997)

これは、2015-07-27 23:59:59.999この範囲内の値の場合ではありません:2015-07-27 23:59:59.997および2015-07-28 0:00:00.000

この範囲は、最も近い前後のオプションに対応し、両方とも.000、.003、または.007で終わります。

切り上げまたは切り下げ

それは2015-07-28 0:00:00.000(+1対-2)に近いため2015-07-27 23:59:59.997、文字列は切り上げられ、このDatetime値:になり2015-07-28 0:00:00.000ます。

上限2015-07-27 23:59:59.998(または.995、.996、.997、.998)2015-07-27 23:59:59.997を使用すると、切り捨てられ、クエリは期待どおりに機能します。しかし、それは解決策ではなく、幸運な価値でした。

Datetime2またはTimeタイプ

DATETIME2と時刻の時間範囲は、00:00:00.0000000スルー23:59:59.9999999は100nsの精度(7桁の精度で使用される最後の桁)で。

ただし、Datetime(3)の範囲は、Datetimeの範囲とは異なります。

  • 日時0:0:00.00023:59:59.997
  • DATETIME2 0:0:00.00000000023:59:59.999

溶液

最終的には、その日の最後の時刻と思われる日付以下の日付よりも翌日の日付を探す方が安全です。これは主に、翌日は常に0:00:00.000から始まるが、異なるデータ型は1日の終わりに同じ時刻にならない場合があることがわかっているためです。

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.000正確な結果が得られ、最良のオプションです
  • <= 2015-07-27 23:59:59.xxx 想定される値に切り上げられない場合、予期しない値を返す可能性があります。
  • 日付への変換と関数の使用は、インデックスの使用を制限するため避けるべきです

[posted_date]をDatetime2に変更し、その精度を上げることでこの問題を解決できると考えることもできますが、文字列はまだDatetimeに変換されているため、助けにはなりません。ただし、キャストが追加された場合cast(2015-07-27 23:59:59.999' as datetime2)、これは正常に機能します

キャストと変換

Castは、最大3桁の値をDatetimeに、または最大9桁の値をDatetime2またはTimeに変換して、正しい精度に丸めることができます。

Cast of Datetime2とTime2は異なる結果を与える可能性があることに注意してください。

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) 2015-05-03 00:00:00.0000000を切り上げます(999999949より大きい値の場合)
  • select cast('23:59:59.999999999' as time(7)) => 23:59:59.9999999

日付時刻が0、3、および7の増分で発生する問題を修正しますが、翌日の最初のナノ秒(常に0:00:00.000)より前の日付を検索することは常により適切です。

ソースMSDN:日時(Transact-SQL)


6

丸めています

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

.998、.997、.996、.995すべてが.997にキャスト/ラウンド

使用すべき

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

または

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

このリンクの精度を参照してください。
常に.000、.003、.007として報告されます


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.