MySQLで日付を期間指定!BETWEENの使い方をマスターしよう
1. はじめに:なぜ日付の期間指定が重要なのか?
データベースに蓄積されたデータは、その「いつ」発生したか、という情報が非常に重要です。特定の期間に限定したデータを分析したり、レポートを作成したり、特定のイベントが発生した期間のトランザクションを抽出したりと、ビジネスやアプリケーションの要件に応じて「日付の期間指定」は頻繁に必要とされます。
例えば、
- 過去1週間の売上データを集計したい。
- 先月のサイトへのアクセス数を調べたい。
- 特定のキャンペーン期間中に登録したユーザーをリストアップしたい。
- 年度末の特定の期間に行われた在庫変動を追跡したい。
このようなタスクを実行するには、データベースから日付や日時に関するカラムの値を用いて、特定の範囲内の行を選択する必要があります。
MySQLでは、日付や時間の期間を指定する方法がいくつかあります。主なものとしては、
- 比較演算子 (
>=
および<=
) を組み合わせる方法 BETWEEN
演算子を使う方法
があります。どちらの方法も最終的には同じ結果を得られますが、それぞれに特徴があり、使い分けのポイントが存在します。
本記事では、特に「BETWEEN
演算子」に焦点を当て、その基本的な使い方から、MySQLの多様な日付/時刻データ型との連携、期間指定における注意点、他の方法との比較、そして実践的な応用例まで、詳細かつ徹底的に解説します。この記事を読めば、MySQLで日付の期間指定を行う際に BETWEEN
を効果的に使いこなせるようになるでしょう。
2. MySQLにおける日付と時刻のデータ型
BETWEEN
演算子を使って日付期間を指定する前に、MySQLが扱う日付と時刻に関するデータ型を理解しておくことが不可欠です。BETWEEN
の挙動は、対象となるカラムのデータ型によって微妙に異なるためです。MySQLには主に以下の5つの日付/時刻関連のデータ型があります。
- DATE: 日付を
YYYY-MM-DD
形式で格納します。時間情報は含まれません。- 範囲:
'1000-01-01'
から'9999-12-31'
- 例:
'2023-10-27'
- 範囲:
- TIME: 時刻を
HH:MM:SS
形式(またはより詳細な形式)で格納します。日付情報は含まれません。- 範囲:
'-838:59:59'
から'838:59:59'
(時間差を表すのにも使えるため、24時間を超えることもあります) - 例:
'14:30:00'
- 範囲:
- DATETIME: 日付と時刻を
YYYY-MM-DD HH:MM:SS
形式で格納します。タイムゾーンの影響を受けません(格納時・取得時に変換されない)。- 範囲:
'1000-01-01 00:00:00'
から'9999-12-31 23:59:59'
- 例:
'2023-10-27 14:30:00'
- 範囲:
- TIMESTAMP: 日付と時刻を
YYYY-MM-DD HH:MM:SS
形式で格納しますが、タイムゾーンの影響を受けます。通常、UTC(協定世界時)として内部的に格納され、セッションのタイムゾーンに応じて変換されて表示されます。- 範囲:
'1970-01-01 00:00:01'
UTC から'2038-01-19 03:14:07'
UTC (UNIXエポックからの秒数に基づいているため、範囲に制限があります。MySQL 8.0.28以降では拡張されていますが、互換性に注意が必要です。) - 例:
'2023-10-27 14:30:00'
(表示はタイムゾーンによる)
- 範囲:
- YEAR: 年を4桁または2桁で格納します。
- 範囲: 70 (1970) から 69 (2069) (2桁の場合)、1901 から 2155 (4桁の場合)、または 0000
- 例:
2023
BETWEEN
演算子を使って日付の「期間」を指定する場合、主に DATE
, DATETIME
, TIMESTAMP
型のカラムが対象となります。TIME
型は時間単独の範囲指定、YEAR
型は特定の年の指定に用いられますが、一般的に「日付の期間」という意味で BETWEEN
を使うのは前述の3つの型に対してです。
これらのデータ型の特性、特に DATETIME
と TIMESTAMP
のタイムゾーンに関する違いや、時間情報を含むか含まないかという点は、BETWEEN
を使った期間指定で正確な結果を得るために非常に重要になります。
3. BETWEEN演算子の基本
BETWEEN
演算子は、ある値が指定された範囲内にあるかどうかを判定するために使用されます。構文は以下の通りです。
sql
expr BETWEEN min AND max
この式は、expr
が min
以上 (>=
) かつ max
以下 (<=
) である場合に真 (TRUE) となります。論理的には、以下の式と完全に等価です。
sql
expr >= min AND expr <= max
BETWEEN
演算子は、数値、文字列、そして日付/時刻データ型に適用できます。日付/時刻データ型に対する BETWEEN
は、日付や時刻の順序関係に基づいて範囲判定を行います。
例えば、数値型の場合:
10 BETWEEN 5 AND 15
は真 (TRUE) です。
10 BETWEEN 12 AND 20
は偽 (FALSE) です。
文字列型の場合(辞書順):
'banana' BETWEEN 'apple' AND 'cherry'
は真 (TRUE) です。
'grape' BETWEEN 'apple' AND 'cherry'
は偽 (FALSE) です。
日付/時刻型の場合:
'2023-10-27' BETWEEN '2023-10-01' AND '2023-10-31'
は真 (TRUE) です。
'2023-11-01' BETWEEN '2023-10-01' AND '2023-10-31'
は偽 (FALSE) です。
このように、BETWEEN
は指定した min
と max
を両端に含む範囲を指定する際に非常に直感的で便利な構文を提供します。
4. 日付指定におけるBETWEENの使い方
では、具体的な日付/時刻データ型に対する BETWEEN
の使い方を見ていきましょう。
4.1. DATE型カラムに対するBETWEEN
DATE
型のカラムは時間情報を含まないため、期間指定は比較的シンプルです。BETWEEN
を使うと、指定した開始日から終了日までの丸一日を含む範囲を指定できます。
構文:
sql
SELECT columns
FROM your_table
WHERE date_column BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD';
例:orders
テーブルから2023年10月中の注文を取得する(order_date
カラムが DATE
型の場合)
sql
SELECT order_id, order_date, amount
FROM orders
WHERE order_date BETWEEN '2023-10-01' AND '2023-10-31';
このクエリは、order_date
が '2023-10-01'
以上かつ '2023-10-31'
以下であるすべての行を選択します。DATE
型なので、それぞれの日の00:00:00から23:59:59まで、つまりその日全体が範囲に含まれます。
開始日や終了日は、リテラル文字列だけでなく、MySQLの関数や他のカラム、ユーザー定義変数などを使って動的に指定することも可能です。
例:現在の日付から過去7日間のデータを取得する(event_date
カラムが DATE
型の場合)
sql
SELECT event_id, event_date, description
FROM events
WHERE event_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE();
CURDATE()
は現在の日付を返します。DATE_SUB(CURDATE(), INTERVAL 7 DAY)
は現在の日付から7日前を計算します。これにより、過去7日間(今日を含む)のデータを簡単に取得できます。
DATE
型に対する BETWEEN
は直感的で分かりやすいですが、時間情報がないため、時間単位での期間指定が必要な場合には使用できません。
4.2. DATETIME/TIMESTAMP型カラムに対するBETWEEN
DATETIME
型や TIMESTAMP
型のカラムは、日付と時刻の両方の情報を含んでいます。このデータ型に対して BETWEEN
を使う場合、時刻の情報も考慮されるという重要な点に注意が必要です。
構文:
sql
SELECT columns
FROM your_table
WHERE datetime_column BETWEEN 'YYYY-MM-DD HH:MM:SS' AND 'YYYY-MM-DD HH:MM:SS';
例:logs
テーブルから2023年10月27日の14:00:00から15:00:00までのログを取得する(log_time
カラムが DATETIME
型の場合)
sql
SELECT log_id, log_time, message
FROM logs
WHERE log_time BETWEEN '2023-10-27 14:00:00' AND '2023-10-27 15:00:00';
これは期待通りの結果を返します。しかし、日付全体、例えば「2023年10月中のすべてのログ」を取得したい場合に、安易に以下のように指定すると問題が発生する可能性があります。
sql
-- 意図:2023年10月1日 00:00:00 から 2023年10月31日 23:59:59 までのログを取得したい
SELECT log_id, log_time, message
FROM logs
WHERE log_time BETWEEN '2023-10-01' AND '2023-10-31'; -- ★ ここに注意!
このクエリは、内部的には以下のように解釈されます(時間部分が省略された場合、通常は00:00:00が補われます):
sql
WHERE log_time BETWEEN '2023-10-01 00:00:00' AND '2023-10-31 00:00:00';
したがって、2023年10月31日の00:00:00より後のログは含まれません! 10月31日中に発生したほとんどのログが漏れてしまいます。
これは BETWEEN
の仕様(両端を含む >= AND <=
)によるものです。期間の終端に指定した値(’2023-10-31′ は ‘2023-10-31 00:00:00’ と見なされる)より大きい値は含まれません。
【DATETIME/TIMESTAMP型で日付全体を期間指定する際の対策】
期間の終端を含む日付全体を指定したい場合は、終了日の時刻を明示的にその日の終わり(通常は 23:59:59)に設定する必要があります。
sql
-- 正しい方法:2023年10月1日 00:00:00 から 2023年10月31日 23:59:59 までのログを取得
SELECT log_id, log_time, message
FROM logs
WHERE log_time BETWEEN '2023-10-01 00:00:00' AND '2023-10-31 23:59:59';
または、日付リテラルを使う場合は、終了日の翌日の00:00:00を使い、BETWEEN
ではなく >=
と <
を組み合わせる方法がより確実で推奨されることがあります。
sql
-- 別な正しい方法:2023年10月1日 00:00:00 から 2023年11月1日 00:00:00 未満のログを取得
SELECT log_id, log_time, message
FROM logs
WHERE log_time >= '2023-10-01 00:00:00' AND log_time < '2023-11-01 00:00:00';
この カラム >= 開始日時 AND カラム < 終了日時の翌日の00:00:00
というパターンは、DATETIME/TIMESTAMP型で「特定の日付全体」を含む期間を指定する際によく用いられます。BETWEEN
を使う場合は終了日の時刻を 23:59:59
に設定する必要がありますが、秒以下の精度を持つ場合(例: DATETIME(3)
でミリ秒まで格納)は 23:59:59.999...
のように指定する必要があり、これもまた面倒です。一方、>= AND <
のパターンであれば、終了日の翌日の00:00:00を指定するだけで、その日の最後まで(秒以下の精度も含めて)正確に範囲に含めることができます。
TIMESTAMP型に関する注意点
TIMESTAMP
型はタイムゾーンの影響を受けます。BETWEEN
で指定するリテラル値は、通常、MySQLサーバーまたは現在のセッションのタイムゾーンで解釈されます。カラムに格納されている値はUTCですが、比較時にはタイムゾーン変換が行われます。これは複雑な問題を引き起こす可能性があり、特にクライアントとサーバーのタイムゾーン設定が異なる場合や、期間をまたぐサマータイムの切り替わりなどでは注意が必要です。一般的には、アプリケーション側でタイムゾーンを考慮した正確な日時文字列を生成してクエリに渡すか、サーバーのタイムゾーン設定を統一するといった対策が取られます。
まとめると、DATETIME
/TIMESTAMP
型に対して BETWEEN
を使う場合は、期間の終端に時刻情報を含めて正確に指定することが非常に重要です。日付全体を指定したい場合は、終端の時刻を 23:59:59
とするなどの工夫が必要ですが、>= AND <
のパターンの方が安全で確実な場合が多いということも覚えておきましょう。
5. BETWEENの利点と欠点
BETWEEN
演算子を日付期間指定に使うことには、いくつかの利点と欠点があります。
5.1. 利点
- 構文の簡潔さと可読性:
expr BETWEEN min AND max
という形式は、「〜から〜まで」という人間の思考パターンに合致しており、直感的で分かりやすいです。>= min AND <= max
と比較して、特に簡単な期間指定においてはクエリの可読性が向上します。 - シンプルなケースでの使いやすさ: DATE型のように時間情報を考慮する必要がない場合や、DATETIME/TIMESTAMP型でも時間の精度を問わない(または開始・終了時刻が明確な)期間を指定する場合には、
BETWEEN
は非常に便利です。
5.2. 欠点
- DATETIME/TIMESTAMPの終端問題: 前述のように、DATETIME/TIMESTAMP型で日付全体を期間に含めたい場合、終了日の時刻を
23:59:59
などに明示的に指定しないと意図した結果が得られないことがあります。この「終端を含む」という仕様が、かえって間違いを引き起こす原因になることがあります。 - 秒以下の精度への対応: DATETIME(N) や TIMESTAMP(N) のように秒以下の精度を持つデータ型の場合、終端を正確に含めるために
23:59:59.999999
のように指定するのは煩雑です。 - 機能的な差異の限定:
BETWEEN
は基本的に>= AND <=
の糖衣構文(Syntactic Sugar)です。パフォーマンス上の特別な利点があるわけではありません。MySQLのオプティマイザは通常、これらを同じように扱います。
6. BETWEENと他の日付期間指定方法の比較
BETWEEN
以外にも、MySQLで日付期間を指定する方法はあります。それぞれの特徴を理解し、使い分けることが重要です。
6.1. >= AND <=
との比較
これは最も基本的な方法であり、BETWEEN
と機能的に等価です。
“`sql
— BETWEEN を使う場合
WHERE column BETWEEN ‘2023-10-01’ AND ‘2023-10-31’;
— >= AND <= を使う場合
WHERE column >= ‘2023-10-01’ AND column <= ‘2023-10-31’;
“`
どちらを使うかは、主に個人の好みやチームのコーディング規約によります。ただし、DATETIME/TIMESTAMP型で日付全体を指定する際には、>= AND <
のパターンが推奨されることがあります。
sql
-- DATETIME/TIMESTAMPで2023年10月全体を含む(推奨パターン)
WHERE datetime_column >= '2023-10-01 00:00:00' AND datetime_column < '2023-11-01 00:00:00';
このパターンは、終端が「含まれない (<
)」ため、終了日の翌日の日付を時間の指定なしで指定するだけで、その日の00:00:00の瞬間まで(つまり前日の23:59:59.999…まで)を正確に含めることができます。この点において、BETWEEN
で終端を 23:59:59
と指定するよりも間違いが少なく、秒以下の精度を持つ型にも対応しやすいという利点があります。
6.2. DATE()
関数との組み合わせ
DATETIME
や TIMESTAMP
型のカラムに対して、日付部分だけを基準に期間指定したい場合があります。このような場合に DATE()
関数が役立ちます。
sql
-- DATETIMEカラムで、日付部分が2023年10月中のデータを取得
SELECT log_id, log_time, message
FROM logs
WHERE DATE(log_time) BETWEEN '2023-10-01' AND '2023-10-31';
このクエリは、log_time
の日付部分が '2023-10-01'
以上かつ '2023-10-31'
以下である行を選択します。これにより、DATETIME
カラムの値が何時であろうと、日付が範囲内であれば含まれるようになります。これは DATE
型カラムに対する BETWEEN
と同様の挙動になります。
注意点: DATE(log_time)
のようにカラムに関数を適用すると、通常、そのカラムに張られているインデックスが利用されなくなります(ただし、MySQL 8.0以降の関数ベースインデックスや、特定の条件下では利用されることもあります)。テーブルのサイズが大きい場合、これはパフォーマンスの低下につながる可能性があります。
パフォーマンスを重視する場合、特に期間が連続している場合は、関数を使わずに比較演算子や BETWEEN
で期間を絞り込む方がインデックスを利用できて高速になる傾向があります。
sql
-- パフォーマンスが良い可能性のある代替方法 (DATETIMEの場合)
SELECT log_id, log_time, message
FROM logs
WHERE log_time >= '2023-10-01 00:00:00' AND log_time < '2023-11-01 00:00:00';
-- または
-- WHERE log_time BETWEEN '2023-10-01 00:00:00' AND '2023-10-31 23:59:59';
DATE()
関数を使うべきか、時間まで指定して比較演算子を使うべきかは、データ型、必要な精度、パフォーマンス要件によって判断します。日付部分だけを基準にしたい場合は DATE()
が便利ですが、パフォーマンスが懸念される場合は代替手段を検討する必要があります。
6.3. YEAR()
, MONTH()
, DAY()
関数との組み合わせ
特定の年、月、または日のデータを抽出したい場合にこれらの関数が使われます。これは連続した「期間」というよりは、特定の属性(年、月、日)によるフィルタリングです。
“`sql
— 2023年のデータを取得
SELECT … FROM your_table WHERE YEAR(date_column) = 2023;
— 10月のデータを取得
SELECT … FROM your_table WHERE MONTH(date_column) = 10;
— 2023年10月のデータを取得
SELECT … FROM your_table WHERE YEAR(date_column) = 2023 AND MONTH(date_column) = 10;
— 毎月1日のデータを取得
SELECT … FROM your_table WHERE DAY(date_column) = 1;
“`
これらの方法は、非連続な条件を指定するのに便利ですが、連続した期間(例: 2023年10月15日から11月10日まで)を指定するには適していません。また、これらの関数もカラムに適用されるため、インデックスが効きにくくなる可能性がある点に注意が必要です。
BETWEENは、連続した日付や日時の範囲を指定する際に最も力を発揮します。一方、YEAR()
, MONTH()
, DAY()
などは、期間よりも特定の「日付属性」による抽出に適しています。
7. BETWEENとNULL値
BETWEEN
演算子を使用する際、対象となるカラムの値や、指定する min
, max
の値が NULL
である場合の挙動を理解しておくことも重要です。
MySQLにおいて、NULL
と他の値(NULL
自身を含む)との比較は、基本的に結果が NULL
となります(UNKNOWN)。WHERE句では、条件が真 (TRUE) となる行のみが選択されるため、結果が NULL
となる行は含まれません。
つまり、expr BETWEEN min AND max
の式において、
expr
がNULL
の場合、結果はNULL
min
がNULL
の場合、結果はNULL
max
がNULL
の場合、結果はNULL
となります。したがって、期間指定の対象となるカラムに NULL
が含まれている場合、その行は BETWEEN
条件を満たさないと判断され、クエリ結果には含まれません。
例:end_date
カラムに NULL
が含まれている場合
sql
SELECT task_id, start_date, end_date
FROM tasks
WHERE end_date BETWEEN '2023-10-01' AND '2023-10-31';
このクエリは、end_date
が NULL
であるタスクは、たとえ start_date
が期間内であっても結果に含めません。
もし、NULL
の行も特定のロジックに基づいて含めたい場合は、OR
句を使って IS NULL
条件を追加する必要があります。
例:終了日が指定期間内であるタスク、または終了日が未定(NULL
)であるタスクを取得する
sql
SELECT task_id, start_date, end_date
FROM tasks
WHERE (end_date BETWEEN '2023-10-01' AND '2023-10-31') OR (end_date IS NULL);
このように、BETWEEN
条件だけでなく、NULL
値をどのように扱いたいかに応じて IS NULL
や IS NOT NULL
を適切に組み合わせる必要があります。
8. 応用例と実践的なテクニック
ここでは、BETWEEN
演算子と他のMySQL関数を組み合わせて、より複雑で実践的な日付期間指定を行う方法を見ていきましょう。特に、現在の日付を基準とした動的な期間指定はよく使われます。
8.1. 動的な期間指定
現在の日付や時刻を基準に期間を指定するには、CURDATE()
, NOW()
, DATE_SUB()
, DATE_ADD()
, LAST_DAY()
などの関数を組み合わせます。
例1:過去7日間のデータを取得する
event_date
(DATE型) カラムがあるとします。今日を含めた過去7日間(今日 – 6日から今日まで)のデータを取得します。
sql
SELECT event_id, event_date, description
FROM events
WHERE event_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 6 DAY) AND CURDATE();
DATE_SUB(CURDATE(), INTERVAL 6 DAY)
は、現在の日付から6日前を計算します。BETWEEN
は両端を含むため、これで今日から6日前までの計7日間の範囲が指定できます。
例2:今週のデータを取得する(月曜日始まり)
order_date
(DATE型) カラムがあるとします。現在の日付を含む週の月曜日から日曜日までのデータを取得します。MySQLの DAYOFWEEK()
は日曜日を1、月曜日を2と返します。月曜日を週の始まりとするには少し工夫が必要です。DAYOFWEEK()
で得た曜日から2を引き、負の値の場合は6を足すか、DAYOFWEEK()
から1を引いて日曜を0とした後、WEEKDAY()
関数(月曜0、火曜1…日曜6)を使うと便利です。
sql
-- WEEKDAY() を使う方法 (月曜:0, ..., 日曜:6)
SELECT order_id, order_date, amount
FROM orders
WHERE order_date BETWEEN DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) AND DATE_ADD(CURDATE(), INTERVAL 6 - WEEKDAY(CURDATE()) DAY);
DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)
は、今日から今日の曜日(WEEKDAY()
は月曜日を0とする)の日数分を引くことで、その週の月曜日を計算します。
DATE_ADD(CURDATE(), INTERVAL 6 - WEEKDAY(CURDATE()) DAY)
は、今日の曜日(月曜0)から6日後(つまり日曜日)を計算します。
例3:今月のデータを取得する
sale_time
(DATETIME型) カラムがあるとします。今月1日の00:00:00から今月末日の23:59:59までのデータを取得します。
sql
SELECT sale_id, sale_time, amount
FROM sales
WHERE sale_time BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01 00:00:00') AND CONCAT(LAST_DAY(CURDATE()), ' 23:59:59');
DATE_FORMAT(CURDATE(), '%Y-%m-01')
は、現在の日付からその月の1日を表す文字列 (‘YYYY-MM-01’) を生成します。これに ‘ 00:00:00’ を結合して期間の開始日時とします。
LAST_DAY(CURDATE())
は、現在の日付を含む月の最終日を返します。これに ‘ 23:59:59’ を結合して期間の終了日時とします。
DATETIME/TIMESTAMP型で終端を含む日付全体を指定する場合、この CONCAT(LAST_DAY(CURDATE()), ' 23:59:59')
のパターンは、日付の計算と文字列操作を組み合わせることで安全な終端指定を実現します。
例4:過去30日間のデータを取得する (DATETIME)
access_time
(TIMESTAMP型) カラムがあるとします。今日を含めた過去30日間(今日 – 29日 00:00:00 から 今日 23:59:59 まで)のデータを取得したいとします。
sql
SELECT user_id, access_time, page_url
FROM access_logs
WHERE access_time BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 29 DAY) AND CONCAT(CURDATE(), ' 23:59:59');
NOW()
は現在の正確な日時を返しますが、期間の開始点を日付の始まりにしたいので DATE(NOW())
で日付部分だけを取り出し、そこから29日前を計算しています。期間の終点は今日の終わりを指定しています。
あるいは、>= AND <
のパターンを使うと、よりシンプルになる場合があります。
sql
-- >= AND < パターンで過去30日間 (DATETIME/TIMESTAMP)
SELECT user_id, access_time, page_url
FROM access_logs
WHERE access_time >= DATE_SUB(DATE(NOW()), INTERVAL 29 DAY) AND access_time < DATE_ADD(DATE(NOW()), INTERVAL 1 DAY);
これは「今日の00:00:00」の翌日、つまり「明日の00:00:00」未満を指定することで、今日の23:59:59.999…までを含む範囲を表現しています。どちらの方法を使うかは、可読性や精度(秒以下まで考慮するか)によって選択します。
8.2. BETWEENと他の条件の組み合わせ
BETWEEN
は WHERE
句の一部として、他の条件と AND
や OR
で自由に組み合わせることができます。
例:2023年10月中に発生し、かつステータスが ‘Completed’ の注文を取得する
sql
SELECT order_id, order_date, amount, status
FROM orders
WHERE order_date BETWEEN '2023-10-01' AND '2023-10-31'
AND status = 'Completed';
例:特定のユーザーの、2023年中のログインログを取得する
sql
SELECT log_id, log_time, user_id
FROM login_logs
WHERE log_time BETWEEN '2023-01-01 00:00:00' AND '2023-12-31 23:59:59'
AND user_id = 123;
8.3. インデックスの利用
大規模なテーブルで日付期間指定を行う場合、パフォーマンスは非常に重要です。WHERE
句で指定するカラムに適切にインデックスが張られているか確認しましょう。
BETWEEN
演算子は、条件が column >= min AND column <= max
と等価であるため、カラムにB-treeインデックスが張られていれば、そのインデックスを効率的に利用できます。範囲検索はインデックスの得意な操作の一つです。
ただし、前述の DATE(log_time)
のように、カラムに関数を適用してしまうと、通常はインデックスが利用できなくなります(フルテーブルスキャンが発生する可能性が高まります)。
インデックスを有効に活用するためのポイント:
- 期間指定の対象となる日付/時刻カラムにインデックスを作成する。
WHERE
句では、可能な限りカラム自体に関数を適用せず、リテラル値や他の関数の結果(スカラー値)と比較する形式にする。- 良い例:
WHERE date_column BETWEEN '...' AND '...'
- 良い例:
WHERE datetime_column >= '...' AND datetime_column < '...'
- 避けるべき例:
WHERE DATE(datetime_column) BETWEEN '...' AND '...'
(パフォーマンスが重要な場合)
- 良い例:
クエリの実行計画を確認するには EXPLAIN
ステートメントが役立ちます。
sql
EXPLAIN SELECT ... FROM your_table WHERE date_column BETWEEN '...' AND '...';
EXPLAIN
の出力で type
が range
になっていれば、インデックスを使った範囲検索が効率的に行われていることを示唆しています。Extra
カラムに Using index condition
や Using where
といった情報も表示されます。
9. よくある間違いとその対策
BETWEEN
を使った日付期間指定でよく陥りがちな間違いと、その対策をまとめます。
-
間違い1: DATETIME/TIMESTAMPの終端指定漏れ
- 例:
WHERE datetime_column BETWEEN '2023-10-01' AND '2023-10-31';
- 結果: 10月31日の00:00:00より後のデータが含まれない。
- 対策:
- 終端の時間を明示的に指定する:
WHERE datetime_column BETWEEN '2023-10-01 00:00:00' AND '2023-10-31 23:59:59';
- または
>= AND <
のパターンを使用する:WHERE datetime_column >= '2023-10-01 00:00:00' AND datetime_column < '2023-11-01 00:00:00';
こちらの方が秒以下の精度にも対応しやすく安全な場合が多い。
- 終端の時間を明示的に指定する:
- 例:
-
間違い2: 日付/時刻フォーマットの不一致
- MySQLが認識できる日付/時刻リテラルの標準フォーマットは
YYYY-MM-DD HH:MM:SS
またはYYYYMMDDHHMMSS
のような数値形式です。他のフォーマット(例:MM/DD/YYYY
)で指定すると、正しく解釈されないかエラーになります。 - 対策:
WHERE
句でリテラル値を指定する際は、MySQLが認識する標準的なフォーマットを使用する。アプリケーション側で日付文字列を生成する場合は、この点に注意する。 - 例:
WHERE order_date BETWEEN '2023-10-01' AND '2023-10-31';
(OK) - 例:
WHERE order_date BETWEEN '10/01/2023' AND '10/31/2023';
(NG)
- MySQLが認識できる日付/時刻リテラルの標準フォーマットは
-
間違い3: 開始日と終了日の順序ミス
BETWEEN min AND max
は、min
がmax
以下である必要があります。BETWEEN max AND min
のように大小が逆転している場合、常に条件が偽 (FALSE) となり、何も結果が返されません。- 例:
WHERE order_date BETWEEN '2023-10-31' AND '2023-10-01';
- 結果: 常に FALSE となり、行が選択されない。
- 対策:
min
とmax
の値を正しく指定する。動的に値を生成する場合は、大小関係を保証するロジックを入れる。
-
間違い4: タイムゾーンの考慮漏れ (TIMESTAMP型)
TIMESTAMP
型はタイムゾーンの影響を受けます。BETWEEN
で指定したリテラル値はサーバーまたはセッションのタイムゾーンで解釈されるため、データが格納されているUTCとの間で変換が行われます。異なるタイムゾーン設定の環境でクエリを実行すると、意図した期間とずれる可能性があります。- 対策:
- サーバーとクライアント(アプリケーション)のタイムゾーン設定を統一する。
- セッションのタイムゾーンを
SET time_zone = ...;
で明示的に設定してからクエリを実行する。 - 可能であれば、アプリケーション側でUTC時刻に変換した上でクエリに渡す。
- タイムゾーン変換の影響を受けない
DATETIME
型の使用を検討する(ただし、アプリケーション側でタイムゾーン管理が必要になる)。
これらの間違いは、特に DATETIME/TIMESTAMP 型や複数の環境でデータベースを操作する場合に発生しやすいため、注意深く扱う必要があります。
10. まとめ
MySQLで日付の期間を指定することは、データ操作の基本であり、非常に頻繁に行われるタスクです。BETWEEN
演算子は、この期間指定を直感的で簡潔な構文で記述できる便利なツールです。
本記事では、BETWEEN
の基本的な使い方から始め、MySQLの日付/時刻データ型(特に DATE
, DATETIME
, TIMESTAMP
)との連携、期間指定における重要な注意点、他の方法(>= AND <=
, DATE()
関数など)との比較、NULL値の扱い、そして動的な期間指定やインデックス利用といった実践的なテクニックまでを詳細に解説しました。
DATE
型に対してBETWEEN
を使うのは最もシンプルで、日付全体の期間指定に迷うことは少ないでしょう。DATETIME
/TIMESTAMP
型に対してBETWEEN
を使う場合は、期間の終端に時刻情報を含めて正確に指定することが最も重要な注意点です。日付全体を期間に含めたい場合は、終了日の時刻を23:59:59
にするか、より確実な>= start_datetime AND < end_datetime_of_next_day
というパターンを検討しましょう。BETWEEN
は可読性が高いですが、機能的には>= AND <=
とほぼ同じです。どちらを使うかは状況と好みに応じて選択できます。DATE()
関数などを併用することで、DATETIME/TIMESTAMPカラムの日付部分のみを基準に期間指定できますが、パフォーマンスへの影響(インデックス不利用)に注意が必要です。- 動的な期間指定には
CURDATE()
,NOW()
,DATE_SUB()
,DATE_ADD()
などの関数を組み合わせるのが一般的です。 - 大規模なテーブルでは、対象カラムにインデックスが張られていること、そして
WHERE
句の条件がインデックスを利用できる形式になっていることが、クエリのパフォーマンスを大きく左右します。
MySQLでの日付期間指定は、これらの基本的な概念と注意点を理解していれば、正確かつ効率的に行うことができます。本記事で解説した内容を参考に、ご自身のデータベース操作に BETWEEN
演算子や関連するテクニックを自信を持って活用してください。