sql >> データベース >  >> RDS >> Sqlserver

DATEDIFF()はSQL Serverで間違った結果を返しますか?これを読む。

    DATEDIFF()を使用したときに非常に奇妙な結果が得られた場合 SQL Serverの関数であり、関数にバグが含まれていると確信している場合は、まだ髪の毛を引き裂かないでください。おそらくバグではありません。

    この関数によって生成される結果がかなり奇抜になる可能性があるシナリオがあります。また、関数が実際にどのように機能するかを理解していないと、結果は完全に間違って見えます。

    この記事がDATEDIFF()の方法を明確にするのに役立つことを願っています 関数は機能するように設計されており、期待どおりの結果が得られない可能性のあるシナリオの例をいくつか提供します。

    例1–365日は必ずしも1年ではありません

    質問: 365日はいつですか 1年?

    回答: DATEDIFF()を使用する場合 もちろん!

    DATEDIFF()を使用する例を次に示します。 2つの日付の間の日数を返し、次に同じ2つの日付の間の年数を返します。

    DECLARE 
      @startdate datetime2 = '2016-01-01  00:00:00.0000000', 
      @enddate datetime2 = '2016-12-31 23:59:59.9999999';
    SELECT 
      DATEDIFF(day, @startdate, @enddate) Days,
      DATEDIFF(year, @startdate, @enddate) Years;
    

    結果:

    +--------+---------+
    | Days   | Years   |
    |--------+---------|
    | 365    | 0       |
    +--------+---------+
    

    この結果が間違っていると思われる場合は、そのDATEDIFF() 明らかにバグがあります。読み進めてください。すべてが見た目どおりであるとは限りません。

    信じられないかもしれませんが、これは実際には期待される結果です。この結果は、DATEDIFF()の方法と完全に一致しています。 動作するように設計されています。

    例2–100ナノ秒=1年?

    逆に考えてみましょう。

    DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', 
      @enddate datetime2 = '2017-01-01 00:00:00.0000000';
    
    SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
      DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
      DATEDIFF(month,         @startdate,   @enddate) Month,
      DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
      DATEDIFF(day,           @startdate,   @enddate) Day,
      DATEDIFF(week,          @startdate,   @enddate) Week,
      DATEDIFF(hour,          @startdate,   @enddate) Hour,
      DATEDIFF(minute,        @startdate,   @enddate) Minute,
      DATEDIFF(second,        @startdate,   @enddate) Second,
      DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
      DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
      DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;
    

    結果(垂直出力で表示):

    Year        | 1
    Quarter     | 1
    Month       | 1
    DOY         | 1
    Day         | 1
    Week        | 1
    Hour        | 1
    Minute      | 1
    Second      | 1
    Millisecond | 1
    Microsecond | 1
    Nanosecond  | 100
    

    2つの日付/時刻の違いはわずか100ナノ秒(.0000001秒)ですが、ナノ秒を除いて、すべての日付部分でまったく同じ結果が得られます。

    これはどのように起こりますか? 1マイクロ秒の差と1年の差の両方を同時に行うにはどうすればよいでしょうか。間にあるすべての日付部分は言うまでもありませんか?

    クレイジーに見えるかもしれませんが、これもバグではありません。これらの結果は、DATEDIFF()の方法と完全に一致しています。 動作するはずです。

    さらに混乱させるために、データ型に応じて異なる結果を得ることができます。しかし、すぐにそれに到達します。まず、DATEDIFF()がどのように機能するかを見てみましょう。 関数は実際に機能します。

    DATEDIFF()の実際の定義

    結果が得られる理由は、DATEDIFF() 関数は次のように定義されます:

    この関数は、指定された startdate 間で交差した、指定された日付部分の境界のカウントを(符号付き整数値として)返します。 およびenddate

    「datepartの境界を越えた」という言葉に特に注意してください。これが、前の例で行った結果が得られる理由です。 DATEDIFF()と簡単に推測できます 計算に経過時間を使用しますが、使用しません。交差した日付部分の境界の数を使用します。

    最初の例では、日付は年の一部の境界を超えていませんでした。最初のデートの年は、2番目のデートの年とまったく同じでした。境界を越えることはありませんでした。

    2番目の例では、反対のシナリオがありました。日付は、すべての日付部分の境界を少なくとも1回(ナノ秒の場合は100回)超えました。

    例3–1週間の異なる結果

    さて、一年が過ぎたふりをしましょう。そして、ここでは、年の値が1つ増えていることを除いて、日付/時刻の値でちょうど1年後です。

    同じ結果が得られるはずですよね?

    DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', 
      @enddate datetime2 = '2018-01-01 00:00:00.0000000';
    
    SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
      DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
      DATEDIFF(month,         @startdate,   @enddate) Month,
      DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
      DATEDIFF(day,           @startdate,   @enddate) Day,
      DATEDIFF(week,          @startdate,   @enddate) Week,
      DATEDIFF(hour,          @startdate,   @enddate) Hour,
      DATEDIFF(minute,        @startdate,   @enddate) Minute,
      DATEDIFF(second,        @startdate,   @enddate) Second,
      DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
      DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
      DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;
    

    結果:

    Year        | 1
    Quarter     | 1
    Month       | 1
    DOY         | 1
    Day         | 1
    Week        | 0
    Hour        | 1
    Minute      | 1
    Second      | 1
    Millisecond | 1
    Microsecond | 1
    Nanosecond  | 100
    

    間違っています。

    それらのほとんどは同じですが、今回はその週が0を返しました 。

    え?

    これは、入力された日付のカレンダーが同じであるために発生しました。 値。たまたま、たとえば2で選択した日付の暦週の値が異なっていました。

    具体的には、例2は、「2016-12-31」から「2017-01-01」までの週の一部の境界を越えました。これは、2016年の最後の週が2016年12月31日に終了し、2017年の最初の週が2017年1月1日(日曜日)に開始されたためです。

    しかし、例3では、2018年の最初の週は実際には2017年12月31日(日曜日)の開始日に始まりました。翌日である私たちの終了日は、同じ週内に収まりました。したがって、週の一部の境界を越えることはありませんでした。

    これは明らかに、日曜日が毎週の最初の日であることを前提としています。結局のところ、DATEDIFF() 関数 日曜日が週の最初の日であると想定します。 SET DATEFIRSTも無視します 設定(この設定では、どの曜日が週の最初の日と見なされるかを明示的に指定できます)。 SET DATEFIRSTを無視するMicrosoftの理由 DATEDIFF()を保証するということです 関数は決定論的です。これが問題になる場合の回避策は次のとおりです。

    つまり、一言で言えば、日付/時刻によっては、どの日付部分でも結果が「間違っている」ように見える可能性があります。週の部分を使用すると、結果がさらに間違って見える場合があります。また、SET DATEFIRSTを使用すると、さらに間違って見える可能性があります。 7以外の値(日曜日の場合)で、DATEDIFF()を期待している それを尊重します。

    しかし、結果は間違っていませんし、バグでもありません。これは、関数が実際にどのように機能するかを知らない人にとっては、単なる「落とし穴」です。

    これらの落とし穴はすべて、DATEDIFF_BIG()にも当てはまります。 働き。 DATEDIFF()と同じように機能します 署名されたbigintとして結果を返すことを除いて ( intとは対照的に DATEDIFF()の場合 。

    例4–結果はデー​​タ型によって異なります

    また、入力日付に使用するデータ型が原因で、予期しない結果が生じる可能性があります。多くの場合、結果は入力日のデータ型によって異なります。ただし、DATEDIFF()のせいにすることはできません これは、純粋にさまざまなデータ型の機能と制限によるものです。低精度の入力値から高精度の結果が得られることは期待できません。

    たとえば、開始日または終了日に smalldatetimeがある場合 値の場合、秒とミリ秒は常に0を返します。これは smalldatetimeが原因です。 データ型は分単位でのみ正確です。

    例2をsmalldatetimeを使用するように切り替えるとどうなりますか。 datetime2の代わりに :

    DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
      @enddate smalldatetime = '2017-01-01 00:00:00';
    
    SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
      DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
      DATEDIFF(month,         @startdate,   @enddate) Month,
      DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
      DATEDIFF(day,           @startdate,   @enddate) Day,
      DATEDIFF(week,          @startdate,   @enddate) Week,
      DATEDIFF(hour,          @startdate,   @enddate) Hour,
      DATEDIFF(minute,        @startdate,   @enddate) Minute,
      DATEDIFF(second,        @startdate,   @enddate) Second,
      DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
      DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
      DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;
    

    結果:

    Year        | 0
    Quarter     | 0
    Month       | 0
    DOY         | 0
    Day         | 0
    Week        | 0
    Hour        | 0
    Minute      | 0
    Second      | 0
    Millisecond | 0
    Microsecond | 0
    Nanosecond  | 0
    

    これらがすべてゼロである理由は、両方の入力日付が実際には同一であるためです:

    DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
      @enddate smalldatetime = '2017-01-01 00:00:00';
    SELECT  
      @startdate 'Start Date',   
      @enddate 'End Date';
    

    結果:

    +---------------------+---------------------+
    | Start Date          | End Date            |
    |---------------------+---------------------|
    | 2017-01-01 00:00:00 | 2017-01-01 00:00:00 |
    +---------------------+---------------------+
    

    smalldatetimeの制限 データ型により秒が切り上げられ、それによってフローオンエフェクトが発生し、すべてが切り上げられました。同一の入力値にならなくても、データ型が必要な精度を提供していないため、予期しない結果が生じる可能性があります。


    1. 開発スナップショットのテーブルの一部のみのPostgresダンプ

    2. EntityFramework6の動的MySQLデータベース接続

    3. MySQLの保護-安全なインストールのためのデータアクセス権限の利用

    4. SQL Server Management Studio(SSMS)を使用したデータベース設計の概念パート1