web-dev-qa-db-ja.com

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

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

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

21
lecuong92

あなたが使っているので 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 ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝
_

enter image description here

以下のクエリを使用すると、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
_

enter image description hereクリックして拡大

_DATETIME2_はSQL Server 2008以降で使用されているため、DATETIMEの代わりに使用してください。あなたの状況では、_datetime2_を3桁の精度で使用できます。 datetime2(3)

_datetime2_を使用する利点:

  • 時間コンポーネントで小数点以下7桁までサポートするのに対し、datetimeは小数点以下3桁のみをサポートします。したがって、デフォルトではdatetimeは最も近い_.003 seconds_を次のように丸めるため、丸めの問題が発生します。 _.000_、_.003_または_.007_秒。
  • _datetime2_はdatetimeよりはるかに正確であり、_datetime2_はDATEではなくTIMEおよびdatetimeを制御できます。

参照 :

16
Kin Shah

他のいくつかのコメントやその他の質問への回答で言及したように、コアの問題は_2015-07-27 23:59:59.999_がSQL Serverによって_2015-07-28 00:00:00.000_に丸められていることです。 ドキュメントごと for DATETIME

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

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

Table showing rounding rules

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

このために使用できるいくつかの解決策/回避策があります。

_-- 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 データ型を DATE _posted_date_列のデータ型。

オプション3に関しては、いくつかの問題についての非常に良い説明がここにあります: これまでのキャストはargableですが、それは良いアイデアですか?

_.997_小数点以下の秒数が別の マジックナンバー になるため、オプション2と5は好きではありません。 BETWEENが広く採用されていないいくつかの理由により、チェックアウトすることをお勧めします この投稿

比較のためにデータ型を文字列に変換するのは不愉快に思うので、オプション4は好きではありません。 SQL Serverでこれを回避するより定性的な理由は、sargabilityに影響することです。別名、インデックスシークを実行できず、パフォーマンスが低下することがよくあります。

日付範囲クエリを処理する正しい方法と間違った方法の詳細については、チェックアウト この投稿 by Aaron Bertrand を参照してください。

別れでは、元のクエリを維持することができ、_posted_date_列を DATETIME DATETIME2(3) に変換します。これにより、サーバーのストレージスペースが節約され、同じ精度でより高い精度が得られ、より標準に準拠/ポータブルになり、将来ニーズが変化した場合に精度/精度を簡単に調整できます。ただし、これはSQL Server 2008以降を使用している場合のオプションにすぎません。

ちょっとした雑学として、_1/300_を2番目の精度で DATETIME UNIXからのホールドオーバーであるようです このStackOverflowの回答ごとに共有遺産 を持つSybaseは、同様の_1/300_の DATETIMEおよびTIME データ型に2番目の精度がありますが、それらの最下位桁は、「0」、「3」、および「6」で少し異なります。私の意見では、秒(および3.33ms)の精度の_1/300_は、SQL Serverの 4バイトブロックの時間 なので、不幸なアーキテクチャ上の決定です。 DATETIME データ型は1msの精度を簡単にサポートできます。

19
Erik

暗黙的な変換

私は、posted_dateデータ型がDatetimeであることを想定しています。ただし、文字列(Varchar)は暗黙的にDatetimeに変換されるため、反対側の型がDatetime、Datetime2、または単なるTimeであるかどうかは関係ありません。

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

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

日時の時間範囲

日時の時間範囲は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_よりも_2015-07-27 23:59:59.997_(+1対-2)に近いため、文字列は切り上げられ、日時(_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とTimeの時間範囲は、_00:00:00.0000000_から_23:59:59.9999999_までの精度で100ns(7桁の精度で使用した場合の最後の桁)です。

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

  • 日時_0:0:00.000_から_23:59:59.997_
  • Datetime2 _0:0:00.000000000_から_23:59:59.999_

ソリューション

結局、次の日付よりも下の日付を探す方が、それがその日の最後の時間の断片と同じか、それよりも安全です。これは主に、翌日は常に0:00:00.000から始まるが、異なるデータタイプがその日の終わりに同じ時刻を持たない可能性があることがわかっているためです。

_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_accurateの結果を提供し、最良の選択肢
  • _<= 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に変換し、正しい精度に丸めることができます。

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: datetime(Transact-SQL)

9

丸めです

 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'

これの正確さをご覧ください link
常に.000、.003、.007として報告されます

6
paparazzo