【SQL】BETWEEN完全ガイド|範囲指定の基本・NOT BETWEEN・>=AND<=との違い・日付/文字列・NULL・UPDATE/DELETE・パフォーマンスまで

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 ではなく比較演算子を使います。

数値の範囲指定

最もよく使うのが数値列の範囲指定です。

給与が50,000〜70,000の従業員を抽出
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件
-- 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 を使って「期間内のデータ」を抽出できます。

DATE型の範囲指定
-- 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 として扱われるため、その日のデータが抜け落ちる場合があります。

DATETIME で BETWEEN を使うときの問題
-- 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 を使うと、辞書順(照合順序)で範囲を判定します。

文字列の 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 を使うと、指定した範囲の外側にあるデータを抽出します。

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 と比較演算子の対応
-- 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の行は結果に含まれません。

NULL と BETWEEN
-- 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 句でも使えます。

UPDATE で範囲指定
-- 給与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';
DELETE で範囲指定
-- 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 と IN の使い分け
-- 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:年齢層別の集計

CASE WHEN + BETWEEN で年齢層を分類
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:営業時間内のデータ抽出

TIME型の範囲指定
-- 営業時間(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完全ガイドを参照