はじめに
データベース管理者(DBA)として現場に立っていると、SQLの基本的な構文であるはずのBETWEEN演算子でさえ、実装の意図と結果が乖離し、重大なバグを引き起こす場面に遭遇することがあります。特に、日付データの扱いや境界値の解釈において、開発者が直感に頼ったコーディングを行うことで、データの欠落や重複が発生するケースは後を絶ちません。本記事では、BETWEEN演算子の仕様を再確認し、実務においてどのような点に注意すべきか、またどのような代替案が推奨されるのかを解説します。
BETWEEN演算子の基本仕様
SQLにおけるBETWEEN演算子は、指定した値の範囲内にデータが含まれるかを判定するものです。構文は非常にシンプルで、「カラム名 BETWEEN 値1 AND 値2」と記述します。この式は、数学的な意味で表現すると「値1 <= カラム名 <= 値2」と同義です。つまり、指定した開始値と終了値の両方を含む「両端を含む範囲」を指定する演算子であるという点を、まずは強く意識する必要があります。 例えば、数値型カラムに対して以下のようなクエリを実行した場合を考えてみましょう。 SELECT FROM sales_data WHERE amount BETWEEN 1000 AND 5000; このクエリは、amountカラムの値が1000以上、かつ5000以下であるレコードをすべて抽出します。これは多くの開発者が直感的に理解できる挙動であり、数値の範囲指定においては非常に強力なツールとなります。
日付型データにおける「致命的な罠」
BETWEEN演算子を語る上で避けて通れないのが、日付型(DATETIMEやTIMESTAMP)の扱いです。多くのシステムにおいて、ログの検索や期間集計は日常的なタスクですが、ここでBETWEENを安易に使うと、意図しないデータ漏れが発生します。
例えば、2023年10月1日のデータを抽出したいと考え、以下のように記述したとします。
SELECT FROM access_logs WHERE created_at BETWEEN ‘2023-10-01 00:00:00’ AND ‘2023-10-01 23:59:59’;
一見すると、この日のすべてのログを拾えているように見えます。しかし、もしデータがミリ秒単位まで保持されている場合、23時59分59秒999ミリ秒のデータはどうなるでしょうか。あるいは、データベースの仕様やドライバの設定によっては、この範囲指定から微妙に漏れるデータが発生する可能性があります。
さらに悪い例として、以下のような記述があります。
SELECT FROM access_logs WHERE created_at BETWEEN ‘2023-10-01’ AND ‘2023-10-02’;
多くのデータベースシステムにおいて、’2023-10-02’という文字列は’2023-10-02 00:00:00’と解釈されます。この場合、2023年10月2日の0時0分0秒ちょうどに発生したレコードは含まれますが、それ以降のデータは一切含まれません。運用中に発生するデータの境界値問題の多くは、このように「終了日をどう定義するか」という曖昧さに起因します。
実務における推奨アプローチ
では、日付の範囲指定を安全に行うにはどうすればよいのでしょうか。プロフェッショナルな現場では、BETWEENを使わずに比較演算子(>= および <)を組み合わせる手法がデファクトスタンダードとなっています。
具体的には、以下のように記述します。
SELECT FROM access_logs
WHERE created_at >= ‘2023-10-01 00:00:00’
AND created_at < '2023-10-02 00:00:00';
この書き方のメリットは明白です。開始時間は含み、終了時間は含まないという「半開区間」の考え方を採用することで、ミリ秒単位の端数やデータの精度を気にすることなく、正確に1日分(または指定期間分)を抽出できます。この手法は、プログラムコード側で日付を動的に生成する際にも非常に相性が良く、バグの混入リスクを劇的に下げることができます。
パフォーマンスとインデックスの考慮
DBAの視点からもう一つ重要な要素が、インデックスの有効活用です。BETWEEN演算子を使用した際、オプティマイザが適切にインデックスを利用できないケースは稀ですが、複雑な条件と組み合わさった場合には注意が必要です。
例えば、以下のクエリを見てください。
SELECT FROM orders WHERE status = ‘COMPLETED’ AND order_date BETWEEN ‘2023-01-01’ AND ‘2023-01-31’;
この場合、statusカラムとorder_dateカラムにそれぞれインデックスが貼られていたとしても、DBエンジンがどちらを優先すべきか迷う場合があります。複合インデックス(status, order_date)を適切に設計することで、BETWEENによる範囲検索を効率化できます。
また、BETWEENは「値の範囲」を検索するため、インデックスのカーディナリティ(値の分散度)が低いカラムに対して使用すると、フルスキャンに近い動作を招くことがあります。大量のデータに対してBETWEENを使用する場合は、実行計画(EXPLAIN)を確認し、インデックスが「範囲スキャン(Range Scan)」として機能しているかを必ず検証してください。
可読性とメンテナンス性
コードの可読性という観点では、BETWEENは非常に優れています。SQLを見た瞬間に「範囲指定をしているのだな」と意図が伝わるため、単純な数値比較においては積極的に採用すべきです。しかし、前述した日付の罠や、NULL値が含まれる可能性があるカラムへの適用には注意が必要です。
SQLの仕様上、BETWEENの範囲内にNULL値が含まれている場合、そのレコードは結果セットから除外されます。NULLを考慮する必要があるビジネスロジックであれば、BETWEENだけで条件を完結させず、明示的にIS NULL条件を追加するなどの工夫が求められます。
実務で役立つチェックリスト
最後に、実務でBETWEENを使用する際に確認すべきチェックリストをまとめました。
1. 対象カラムの型は何か?(数値型ならBETWEENで問題なし、日付型なら再考が必要)
2. 終了値の境界は厳密か?(終了値を含む必要があるのか、次の開始値までを含まないのか)
3. インデックスは適切か?(実行計画を確認し、範囲スキャンが行われているか)
4. NULLの混入可能性はあるか?(NULLを含める必要がある場合は、BETWEEN単体では不十分)
5. チーム内でのコーディング規約は統一されているか?(「日付には常に比較演算子を使う」といったルールを設けることが推奨される)
まとめ
BETWEEN演算子は、SQLの学習初期に学ぶ便利な道具ですが、実務においてはその「両端を含む」という仕様が、時に複雑なバグの温床となります。特にDBAとしては、開発者が「なんとなく」でBETWEENを使い、日付の境界値でデータを漏らしている状況を放置してはなりません。
日付データの取り扱いにおいては、半開区間(>= と <)を用いたアプローチを推奨し、数値データに対してはBETWEENの可読性を活かすという、ケースバイケースの使い分けがプロフェッショナルの技術力です。データベースはデータの整合性がすべてです。クエリ一つを書く際にも、その背後にあるデータの性質と、データベースエンジンの挙動を常に想像し、堅牢なSQLを記述することを心がけてください。 日々の運用の中で、「なぜこのデータが漏れているのか?」という問いに直面したとき、真っ先にBETWEENの境界値チェックに立ち返れるかどうかが、あなたのDBAとしての、そしてエンジニアとしての実力を測る試金石となるはずです。論理的かつ安全なSQL運用を心がけ、システムの信頼性を高めていきましょう。

コメント