1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【MySQL】datetime型など日付の加算・減算はdatediffを使う

Posted at

先日、SQLで「前後2日以内のレコードを取得したい」と思い、datetime型の加減算を行ったところ、想定外の件数が返ってきてサーバーのメモリを食いつぶしかけました・・・:sweat_drops:
その時の失敗記録です。

n日以内のレコードを取得したかった

スケジュール系のレコードを保持しているテーブルがあり、内容はこんな感じ:arrow_down:

スケジュール名 開始日(datetime) 終了日(datetime)
出張1 2018-09-20 13:30:00 2018-09-25 24:00:00
展示会 2018-09-23 10:00:00 2018-09-29 18:00:00
休暇 2018-09-29 00:00:00 2018-10-01 24:00:00
社内研修 2018-09-30 09:00:00 2018-10-03 18:30:00
出張2 2018-10-03 19:00:00 2018-10-06 16:30:00

以前、全件取得しようとしたら、あまりの重さに耐えきれず、DBサーバーが落っこちたのです・・・:droplet:
そこで、「開始日が本日含め前後2日以内」という条件でレコードを取得しようとしました。
なので、本日=2018/10/01であれば、以下の2レコードが取得できるはず・・・!

スケジュール名 開始日 終了日
休暇 2018-09-29 00:00:00 2018-10-01 24:00:00
社内研修 2018-09-30 09:00:00 2018-10-03 18:30:00

NGパターン:現在日時curdate()に対して加減算を実行した

select * from xxxTbl
where
  開始日 >= (curdate()-2)      -- 2日前
  and 開始日 <= (curdate()+2)  -- 2日後
;

最初に書いたのが、上記:arrow_up:のSQLです。
この書き方の場合、curdate()が2018/10/1だと、2日前の日付が正しく取得できていませんでした。
内部ではこのように動いていたようです:arrow_down:

select * from xxxTbl
where
  開始日 >= (20181001-2)      -- 開始日 >= 20180999
  and 開始日 <= (20181001+2)  -- 開始日 <= 20181003
;

数値として計算してしまったので、20180999となってしまい、ありえない日付になってしまいました。
その結果、取得できていたのがこちら:arrow_down:

スケジュール名 開始日(datetime) 終了日(datetime)
出張1 2018-09-20 13:30:00 2018-09-25 24:00:00
展示会 2018-09-23 10:00:00 2018-09-29 18:00:00
休暇 2018-09-29 00:00:00 2018-10-01 24:00:00
社内研修 2018-09-30 09:00:00 2018-10-03 18:30:00
「2日前」の判定が出来なくて、過去データをすべて取得していました。
そりゃあ、DBサーバー落っこちるわ(--;

だけど、今思うと、datetime型に対して±2って、日付なのか時間なのか、時間だとしても分なのか秒なのかさっぱりわからないですね・・・。
さらにいうと、このSQLを作っていたのが2018/09/15のような月のど真ん中だったため、curdate()±2でも正しく数値計算が出来てしまっていたのです。

OKパターン:datediffを使って現在日時curdate()との差分を取得する

select * from xxxTbl
where
  datediff(curdate(), 開始日) <= 2       -- 2日前
  and datediff(curdate(), 開始日) >= -2  -- 2日後

datediff() 関数は、2つの日付の差を求める関数です。
第1引数の日付の方が大きければ正の値が返ります。
datediff()を使うと、想定通りの結果が取得できました:star:

datediff()関数の実行結果の具体例

実際にdatediff()を実行してみると以下のようになります:arrow_down:
月をまたいでいても、正しく計算されていることがわかります。

第1引数 第2引数 結果
2018-10-13 2018-09-30 00:00:00 13
2018-10-13 2018-10-11 00:00:00 2
2018-10-13 2018-10-12 00:00:00 1
2018-10-13 2018-10-13 00:00:00 0
2018-10-13 2018-10-14 00:00:00 -1
2018-10-13 2018-10-15 00:00:00 -2

おまけ:テスト不足:cry:

こういう「前後2日以内」みたいな場合は、境界値テストを必ず実施するはずです。
私も、「3日前のレコードは取得できない」など、入念に確認しました。
しかし、月をまたいだ場合・年をまたいだ場合のテストは全く実施していませんでした・・・ :sob:

そもそもの話、テストケースの洗い出しが不十分だったというわけです。
例え、実装の時にこういうバグを仕込んでしまっても、テストで月またぎ・年またぎのケースを実施していれば、気づけたはずだったなぁと。

教訓

  • MySQLで日付計算をする時はdatediff()を使う。
  • 日付計算をする時は、月や年をまたいだパターンもテストすること。
1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?