SQLで「○○以上△△以下」の範囲にあるデータを抽出するには BETWEEN 演算子を使います。比較演算子 >= AND <= で書くよりもシンプルで、可読性も高いのが特長です。
本記事では BETWEEN の基本から、データ型ごとの挙動、NOT BETWEEN、比較演算子との違い、NULLの扱い、UPDATE/DELETE での活用、パフォーマンスまで体系的に解説します。
この記事で分かること
- BETWEEN の基本構文と「以上・以下」の境界ルール
- 数値・日付・文字列それぞれの範囲指定の書き方
- NOT BETWEEN で範囲外を抽出する方法
- BETWEEN と >= AND <= の違い・使い分け
- DATETIME 型で BETWEEN を使うときの落とし穴
- NULL が混ざったときの挙動
- UPDATE・DELETE の WHERE 句での活用
- BETWEEN のインデックス効果とパフォーマンス
BETWEEN の基本構文
BETWEEN は WHERE 句で使い、指定した下限値以上・上限値以下の行を返します。
SELECT 列名 FROM テーブル名 WHERE 列名 BETWEEN 下限値 AND 上限値; -- 意味: 列名 >= 下限値 AND 列名 <= 上限値(両端を含む)
BETWEEN は「以上・以下」(両端を含む):BETWEEN 10 AND 20 は 10 も 20 も含みます。「未満」「より大きい」のように片端を除きたい場合は BETWEEN ではなく比較演算子を使います。
数値の範囲指定
最もよく使うのが数値列の範囲指定です。
SELECT employee_id, name, salary FROM employees WHERE salary BETWEEN 50000 AND 70000; -- 50000 と 70000 も含まれる -- >= AND <= で書いた場合(同じ結果) SELECT employee_id, name, salary FROM employees WHERE salary >= 50000 AND salary <= 70000;
-- 20歳以上30歳以下 SELECT * FROM users WHERE age BETWEEN 20 AND 30; -- 数量が100〜500の在庫 SELECT product_id, stock_quantity FROM inventory WHERE stock_quantity BETWEEN 100 AND 500;
下限と上限の順序を間違えない:BETWEEN 70000 AND 50000 のように上限が下限より小さいと、結果は0件になります。BETWEEN は「下限 <= 値 <= 上限」と解釈されるため、順序は必ず「小さい値 AND 大きい値」です。
-- NG: 上限と下限が逆 → 結果は0件 SELECT * FROM employees WHERE salary BETWEEN 70000 AND 50000; -- 内部的に salary >= 70000 AND salary <= 50000 → 常にFALSE -- OK: 正しい順序 SELECT * FROM employees WHERE salary BETWEEN 50000 AND 70000;
日付の範囲指定
日付型の列に BETWEEN を使って「期間内のデータ」を抽出できます。
-- 2024年3月の注文を抽出 SELECT order_id, order_date, amount FROM orders WHERE order_date BETWEEN '2024-03-01' AND '2024-03-31'; -- 直近7日間 -- MySQL SELECT * FROM access_logs WHERE log_date BETWEEN CURDATE() - INTERVAL 7 DAY AND CURDATE(); -- PostgreSQL SELECT * FROM access_logs WHERE log_date BETWEEN CURRENT_DATE - INTERVAL '7 days' AND CURRENT_DATE; -- SQL Server SELECT * FROM access_logs WHERE log_date BETWEEN DATEADD(DAY, -7, CAST(GETDATE() AS DATE)) AND CAST(GETDATE() AS DATE);
DATETIME 型の落とし穴
DATETIME(時刻を含む)列に BETWEEN を使うと、上限日の時刻が 00:00:00 として扱われるため、その日のデータが抜け落ちる場合があります。
-- NG: 3月31日の 00:00:01 以降のデータが漏れる SELECT * FROM orders WHERE created_at BETWEEN '2024-03-01' AND '2024-03-31'; -- → '2024-03-31 00:00:00' までしか含まれない -- → '2024-03-31 09:30:00' のデータが取れない! -- OK: 上限を翌日未満にする(推奨) SELECT * FROM orders WHERE created_at >= '2024-03-01' AND created_at < '2024-04-01'; -- 翌月1日の「未満」 -- OK: 上限に時刻を明示する SELECT * FROM orders WHERE created_at BETWEEN '2024-03-01 00:00:00' AND '2024-03-31 23:59:59'; -- ※ マイクロ秒を含むDATETIME(6)では 23:59:59.999999 まで必要
DATETIME列では BETWEEN より >= AND < を推奨:BETWEEN は上限を「以下」で判定するため、時刻の精度によって抜け漏れが発生します。created_at >= 開始日 AND created_at < 翌日 のパターンが安全です。日付範囲のより詳しいパターンは日付の範囲指定完全ガイドを参照してください。
文字列の範囲指定
文字列に BETWEEN を使うと、辞書順(照合順序)で範囲を判定します。
-- アルファベットの範囲(A〜Mで始まる名前) SELECT name FROM employees WHERE name BETWEEN 'A' AND 'M'; -- 'Alice', 'Bob', 'Charlie', 'Kate' は含まれる -- 'Mike' は 'M' < 'Mike' なので含まれる('M' 以上) -- 'Nancy' は含まれない('N' > 'M') -- 注意: 'M' と 'Mz' の間も含まれる SELECT name FROM employees WHERE name BETWEEN 'A' AND 'Mz'; -- 'Mike', 'Moses' も含まれる
文字列の BETWEEN は照合順序に依存:大文字・小文字の扱い、日本語の並び順などはデータベースの照合順序(COLLATION)によって異なります。意図しない結果になりやすいため、文字列の範囲指定には LIKE や正規表現を使う方が安全な場合があります。
NOT BETWEEN — 範囲外を抽出する
NOT BETWEEN を使うと、指定した範囲の外側にあるデータを抽出します。
-- 給与が50,000〜70,000の範囲外(50,000未満 または 70,000超) SELECT employee_id, name, salary FROM employees WHERE salary NOT BETWEEN 50000 AND 70000; -- 内部的に salary < 50000 OR salary > 70000 -- 3月以外の注文 SELECT * FROM orders WHERE order_date NOT BETWEEN '2024-03-01' AND '2024-03-31';
-- NOT BETWEEN WHERE salary NOT BETWEEN 50000 AND 70000 -- ↓ 同じ意味 WHERE salary < 50000 OR salary > 70000 -- ↓ NOT で囲んだ場合も同じ WHERE NOT (salary >= 50000 AND salary <= 70000)
BETWEEN と >= AND
結果は同じですが、状況によって使い分けるべき場面があります。
| 比較項目 | BETWEEN | >= AND <= |
|---|---|---|
| 可読性 | 範囲指定の意図が明確 | 条件が複雑な場合に柔軟 |
| 境界 | 両端を含む(以上・以下) | >, <, >=, <= を自由に組み合わせ可能 |
| パフォーマンス | 同じ(オプティマイザが同じ実行計画を生成) | 同じ |
| DATETIME | 上限の時刻問題あり(落とし穴) | > < で「未満」を明示できるため安全 |
使い分けの基準:
- 「○○以上△△以下」の単純な範囲 → BETWEEN(意図が明確で読みやすい)
- 「○○以上△△未満」「○○より大きく△△以下」など片端を除く場合 → 比較演算子
- DATETIME 列の期間指定 → >= AND <(翌日未満パターンが安全)
-- 「10以上20未満」— BETWEEN では表現不可 SELECT * FROM products WHERE price >= 10 AND price < 20; -- 「10より大きく20以下」 SELECT * FROM products WHERE price > 10 AND price <= 20;
NULL の扱い
BETWEEN は NULL に対して UNKNOWN を返します。NULLの行は結果に含まれません。
-- salary が NULL の行はどちらにも含まれない SELECT * FROM employees WHERE salary BETWEEN 50000 AND 70000; -- salary が NULL → FALSE(UNKNOWN)→ 結果に含まれない SELECT * FROM employees WHERE salary NOT BETWEEN 50000 AND 70000; -- salary が NULL → FALSE(UNKNOWN)→ こちらにも含まれない -- NULL も含めたい場合は明示的に OR を追加 SELECT * FROM employees WHERE salary BETWEEN 50000 AND 70000 OR salary IS NULL;
UPDATE・DELETE での活用
BETWEEN は SELECT だけでなく、UPDATE や DELETE の WHERE 句でも使えます。
-- 給与50,000〜60,000の従業員に5%昇給 UPDATE employees SET salary = salary * 1.05 WHERE salary BETWEEN 50000 AND 60000; -- 特定期間の注文ステータスを更新 UPDATE orders SET status = 'archived' WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
-- 1年以上前のアクセスログを削除 DELETE FROM access_logs WHERE log_date BETWEEN '2022-01-01' AND '2023-03-31'; -- IDが100〜200の仮データを削除 DELETE FROM test_data WHERE id BETWEEN 100 AND 200;
BETWEEN と IN の使い分け
BETWEEN は「連続する範囲」に使い、IN は「離散的な値の集合」に使います。
| 演算子 | 適用場面 | 例 |
|---|---|---|
| BETWEEN | 連続する範囲 | price BETWEEN 100 AND 500 |
| IN | 離散的な値の集合 | status IN ('active', 'pending') |
-- BETWEEN: 連続する範囲(1〜10の整数)
SELECT * FROM products WHERE category_id BETWEEN 1 AND 10;
-- IN: 特定の離散値
SELECT * FROM products WHERE category_id IN (1, 3, 5, 7);
-- 両方を組み合わせることも可能
SELECT * FROM orders
WHERE amount BETWEEN 1000 AND 5000
AND status IN ('shipped', 'delivered');
IN句の詳しい使い方はIN・OR・BETWEEN・EXISTS完全ガイドを参照してください。
実務でよく使うパターン
パターン1:年齢層別の集計
SELECT
CASE
WHEN age BETWEEN 0 AND 19 THEN '未成年'
WHEN age BETWEEN 20 AND 29 THEN '20代'
WHEN age BETWEEN 30 AND 39 THEN '30代'
WHEN age BETWEEN 40 AND 49 THEN '40代'
ELSE '50代以上'
END AS age_group,
COUNT(*) AS member_count
FROM users
GROUP BY
CASE
WHEN age BETWEEN 0 AND 19 THEN '未成年'
WHEN age BETWEEN 20 AND 29 THEN '20代'
WHEN age BETWEEN 30 AND 39 THEN '30代'
WHEN age BETWEEN 40 AND 49 THEN '40代'
ELSE '50代以上'
END
ORDER BY age_group;
パターン2:営業時間内のデータ抽出
-- 営業時間(9:00〜18:00)のアクセスログ SELECT * FROM access_logs WHERE CAST(access_time AS TIME) BETWEEN '09:00:00' AND '18:00:00'; -- MySQL: TIME() 関数 SELECT * FROM access_logs WHERE TIME(access_time) BETWEEN '09:00:00' AND '18:00:00';
パターン3:価格帯別の商品検索
-- アプリからの価格帯検索(プレースホルダ利用を想定) SELECT product_id, product_name, price FROM products WHERE price BETWEEN 1000 AND 5000 AND category = '家電' ORDER BY price;
パフォーマンスとインデックス
BETWEEN はインデックスを効率的に活用できる演算子です。
| ポイント | 説明 |
|---|---|
| インデックスが効く | BETWEEN は「範囲スキャン」としてインデックスを利用可能 |
| = AND = と同等 | オプティマイザが BETWEEN を >= AND <= に展開するため差なし |
| 列に関数を適用しない | WHERE YEAR(date) BETWEEN ... はインデックスが効かない |
-- OK: 列に直接 BETWEEN(インデックスが効く) SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31'; -- NG: 列に関数を適用(インデックスが効かない) SELECT * FROM orders WHERE YEAR(order_date) BETWEEN 2024 AND 2024 AND MONTH(order_date) BETWEEN 1 AND 3; -- → 全行スキャンになる可能性がある -- OK: 関数を外して範囲指定に書き直す SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31';
SARGable条件にする:BETWEEN の左辺に列をそのまま書き、関数で包まないようにするとインデックスが効きます。YEAR(col) BETWEEN ... ではなく col BETWEEN '日付' AND '日付' と書きましょう。
まとめ
| 目的 | 書き方 |
|---|---|
| ○○以上△△以下の範囲 | col BETWEEN 下限 AND 上限 |
| 範囲外の抽出 | col NOT BETWEEN 下限 AND 上限 |
| 片端を除く範囲 | col >= 下限 AND col < 上限 |
| DATETIME期間 | col >= 開始日 AND col < 翌日(推奨) |
| 離散値の集合 | col IN (値1, 値2, ...) |
- BETWEEN は両端を含む(以上・以下)。片端を除きたいなら比較演算子を使う
- 下限 AND 上限の順序を間違えると0件になるので注意
- DATETIME列では
>= AND <(翌日未満)パターンが安全 — 詳細は日付の範囲指定完全ガイドを参照 - NULLの行はBETWEENにもNOT BETWEENにも含まれない
- 列に関数を適用せずSARGableな条件にすることでインデックスを活用できる
- IN や LIKE との組み合わせはIN・OR・BETWEEN・EXISTS完全ガイドを参照
