【SQL】MIN関数を使って最小値を取得する方法|GROUP BY・NULL対策・サブクエリ活用まで完全解説

【SQL】MIN関数を使って最小値を取得する方法|GROUP BY・NULL対策・サブクエリ活用まで完全解説 SQL

SQLのMIN関数は、指定した列から最小値を取得する集約関数です。売上の最低額、最も古い日付、最安値の商品など、データ分析では欠かせない機能です。

この記事では、MIN関数の基本構文からGROUP BYHAVINGとの組み合わせ、NULLの扱いサブクエリで該当レコード全体を取得する方法、さらにRDBMS別の違いやパフォーマンスのコツまで、実務で使えるパターンを網羅的に解説します。

この記事で学べること

  • MIN関数の基本構文と数値・日付・文字列での使い方
  • GROUP BYでグループ単位の最小値を取得する方法
  • HAVING句で集計結果を絞り込む方法
  • NULLの扱いとCOALESCEによる対策
  • サブクエリ・ウィンドウ関数で最小値のレコード全体を取得する方法
  • MIN関数 vs ORDER BY + LIMIT の違い
  • CASE WHEN + MINで条件付き最小値を取得する方法
  • RDBMS別の違いとパフォーマンスのコツ
スポンサーリンク
  1. サンプルデータ
  2. MIN関数の基本構文
  3. 基本的な使用例
    1. 数値の最小値を取得
    2. 日付の最小値(最古の日付)を取得
    3. 文字列の最小値を取得
  4. WHERE句との組み合わせ
  5. GROUP BYとの組み合わせ
    1. 部署ごとの最低給与
    2. 複数の集約関数を同時に使用
  6. HAVINGで集計結果を絞り込む
  7. NULLの扱い
    1. NULLを0として扱いたい場合
    2. 結果がNULLの場合にデフォルト値を返す
  8. 最小値のレコード全体を取得する方法
    1. 方法1:サブクエリを使う
    2. 方法2:ORDER BY + LIMIT(MySQL / PostgreSQL)
    3. 方法3:ウィンドウ関数を使う
  9. グループごとの最小値レコードを取得
    1. 相関サブクエリを使う方法
    2. ウィンドウ関数+PARTITION BYを使う方法
  10. MIN関数 vs ORDER BY + LIMIT の違い
  11. RDBMS別の違い
  12. MINをウィンドウ関数として使う
    1. 最小値との差分を計算する
  13. CASE WHEN + MIN で条件付き最小値を取得
    1. 部署ごとの条件付き最小値
    2. 年度ごとの最低給与を横並びで取得
  14. 実務でよく使うパターン
    1. シナリオ1:商品テーブルから最安値を取得
    2. シナリオ2:注文履歴から初回注文日を取得
    3. シナリオ3:ログテーブルから最古のエラー発生日時を取得
    4. シナリオ4:在庫テーブルから最低在庫数の商品を通知
    5. シナリオ5:JOINと組み合わせて最安値の仕入先を取得
  15. パフォーマンスのコツ
  16. よくあるエラーと対処法
    1. エラー例と修正
  17. 集約関数まとめ
  18. まとめ
  19. 関連記事

サンプルデータ

この記事では、以下のemployeesテーブルを使って解説します。

id name department salary hire_date
1 田中太郎 営業 350000 2020-04-01
2 鈴木花子 開発 420000 2019-07-15
3 佐藤一郎 営業 300000 2021-01-10
4 高橋美咲 開発 380000 2022-03-20
5 山田健太 人事 NULL 2023-06-01
6 伊藤恵 人事 320000 2020-09-15
7 渡辺大輔 開発 450000 2018-11-01
8 中村由美 営業 280000 2023-02-14
サンプルデータの作成SQL(クリックで展開)
CREATE TABLE + INSERT
CREATE TABLE employees (
    id         INT PRIMARY KEY,
    name       VARCHAR(50),
    department VARCHAR(20),
    salary     INT,
    hire_date  DATE
);

INSERT INTO employees (id, name, department, salary, hire_date) VALUES
(1, '田中太郎', '営業', 350000, '2020-04-01'),
(2, '鈴木花子', '開発', 420000, '2019-07-15'),
(3, '佐藤一郎', '営業', 300000, '2021-01-10'),
(4, '高橋美咲', '開発', 380000, '2022-03-20'),
(5, '山田健太', '人事', NULL,   '2023-06-01'),
(6, '伊藤恵',   '人事', 320000, '2020-09-15'),
(7, '渡辺大輔', '開発', 450000, '2018-11-01'),
(8, '中村由美', '営業', 280000, '2023-02-14');

MIN関数の基本構文

MIN関数は、指定した列の中から最も小さい値を1つ返す集約関数(集計関数)です。数値・日付・文字列のいずれにも使えます。

基本構文
SELECT MIN(列名)
FROM   テーブル名;

MIN関数の特徴

  • NULL値は自動的に無視される(計算に含まれない)
  • 数値・日付・文字列すべてのデータ型に対応
  • GROUP BYと組み合わせてグループ単位の最小値を取得可能
  • DISTINCTを指定しても結果は変わらない(最小値は1つだけ)

基本的な使用例

数値の最小値を取得

全社員の中で最も低い給与を取得します。

SQL
SELECT MIN(salary) AS min_salary
FROM   employees;
min_salary
280000

id=5の山田健太はsalaryがNULLですが、MIN関数はNULLを自動的にスキップして280000を返します。

日付の最小値(最古の日付)を取得

最も早い入社日を取得します。日付型の列では、最も古い日付が最小値になります。

SQL
SELECT MIN(hire_date) AS earliest_hire
FROM   employees;
earliest_hire
2018-11-01

文字列の最小値を取得

文字列型では辞書順(照合順序)で最も先頭の値が返されます。

SQL
SELECT MIN(department) AS first_dept
FROM   employees;
first_dept
営業

注意:文字列のMINは照合順序(COLLATION)に依存します。日本語の場合、使用するDBの照合順序(utf8mb4_general_ci等)によって結果が変わることがあります。

WHERE句との組み合わせ

WHERE句で条件を絞ったうえでMIN関数を適用できます。

開発部のみの最低給与
SELECT MIN(salary) AS min_salary
FROM   employees
WHERE  department = '開発';
min_salary
380000

開発部の3人(鈴木:420000、高橋:380000、渡辺:450000)のうち最小値の380000が返されます。

2021年以降に入社した社員の最低給与
SELECT MIN(salary) AS min_salary
FROM   employees
WHERE  hire_date >= '2021-01-01';
min_salary
280000

GROUP BYとの組み合わせ

GROUP BY句と組み合わせると、グループ単位の最小値を取得できます。

部署ごとの最低給与

SQL
SELECT   department,
         MIN(salary) AS min_salary
FROM     employees
GROUP BY department;
department min_salary
営業 280000
開発 380000
人事 320000

人事部の山田健太(salary=NULL)は無視され、伊藤恵の320000が人事部の最小値となります。

複数の集約関数を同時に使用

MIN関数は他の集約関数と一緒に使えます。

部署別の給与統計
SELECT   department,
         MIN(salary)   AS min_salary,
         MAX(salary)   AS max_salary,
         AVG(salary)   AS avg_salary,
         COUNT(salary) AS cnt
FROM     employees
GROUP BY department;
department min_salary max_salary avg_salary cnt
営業 280000 350000 310000 3
開発 380000 450000 416667 3
人事 320000 320000 320000 1

ポイント:MAX関数AVG関数COUNT関数と同時に使うことで、データの全体像を素早く把握できます。

HAVINGで集計結果を絞り込む

HAVING句を使えば、GROUP BYの集計結果にさらに条件を指定できます。

最低給与が30万以上の部署のみ
SELECT   department,
         MIN(salary) AS min_salary
FROM     employees
GROUP BY department
HAVING   MIN(salary) >= 300000;
department min_salary
開発 380000
人事 320000

営業部はMIN(salary)=280000で条件を満たさないため除外されます。

注意:WHERE句は集約前のフィルタ、HAVING句は集約後のフィルタです。MIN関数の結果で絞り込むにはHAVINGを使います。WHEREにMIN関数を書くとエラーになります。

NULLの扱い

MIN関数とNULLの関係は実務でよくある落とし穴です。

ケース 結果 説明
NULLが混在 NULLを無視して最小値を返す NULL以外の値で計算
全てNULL NULLを返す 比較対象がない
行が0件 NULLを返す 対象レコードなし

NULLを0として扱いたい場合

NULLを特定の値に変換してから比較するには、COALESCE関数を使います。

NULLを0に変換してMIN
SELECT MIN(COALESCE(salary, 0)) AS min_salary
FROM   employees;
min_salary
0

山田健太のNULLが0に変換され、最小値は0になります。

結果がNULLの場合にデフォルト値を返す

対象が0件の場合にデフォルト値を返す
SELECT COALESCE(MIN(salary), 0) AS min_salary
FROM   employees
WHERE  department = '経理'; -- 該当なし

COALESCE(MIN(...), 0)とすることで、対象データが0件でもNULLではなく0を返せます。アプリケーション側でNULLチェックが不要になります。

最小値のレコード全体を取得する方法

MIN関数は最小値だけを返します。「最も給与が低い社員の名前や部署も知りたい」場合は、サブクエリやJOINを使います。

方法1:サブクエリを使う

WHERE句のサブクエリ
SELECT id, name, department, salary
FROM   employees
WHERE  salary = (SELECT MIN(salary) FROM employees);
id name department salary
8 中村由美 営業 280000

方法2:ORDER BY + LIMIT(MySQL / PostgreSQL)

ORDER BY + LIMIT
SELECT id, name, department, salary
FROM   employees
WHERE  salary IS NOT NULL
ORDER BY salary ASC
LIMIT  1;

方法3:ウィンドウ関数を使う

最小値が同じ値の行が複数ある場合にすべて取得したいときに便利です。

ウィンドウ関数
SELECT id, name, department, salary
FROM (
    SELECT id, name, department, salary,
           RANK() OVER (ORDER BY salary ASC) AS rnk
    FROM   employees
    WHERE  salary IS NOT NULL
) sub
WHERE rnk = 1;
方法 同値複数行 パフォーマンス 適した場面
サブクエリ(WHERE = MIN) 全て取得 良好 汎用的・標準SQL
ORDER BY + LIMIT 1件のみ 最速 1件だけ必要な場合
ウィンドウ関数(RANK) 全て取得 中程度 グループ別の最小値レコード

グループごとの最小値レコードを取得

「各部署で最も給与が低い社員」のように、グループ単位で最小値のレコード全体を取得するパターンです。

相関サブクエリを使う方法

部署別に最低給与の社員を取得
SELECT e.id, e.name, e.department, e.salary
FROM   employees e
WHERE  e.salary = (
    SELECT MIN(e2.salary)
    FROM   employees e2
    WHERE  e2.department = e.department
);
id name department salary
8 中村由美 営業 280000
4 高橋美咲 開発 380000
6 伊藤恵 人事 320000

ウィンドウ関数+PARTITION BYを使う方法

PARTITION BYで部署別の最小値レコードを取得
SELECT id, name, department, salary
FROM (
    SELECT id, name, department, salary,
           ROW_NUMBER() OVER (
               PARTITION BY department
               ORDER BY salary ASC
           ) AS rn
    FROM   employees
    WHERE  salary IS NOT NULL
) sub
WHERE rn = 1;

ポイント:ROW_NUMBERは同値でも1件だけ、RANKは同値を全て取得します。要件に合わせて使い分けましょう。

MIN関数 vs ORDER BY + LIMIT の違い

ORDER BY + LIMITでも最小値は取れるのでは?」というのはよくある疑問です。

MIN関数
-- 最小値のみ取得
SELECT MIN(salary) FROM employees;
ORDER BY + LIMIT
-- レコード全体を取得
SELECT * FROM employees
ORDER BY salary ASC
LIMIT 1;
比較項目 MIN関数 ORDER BY + LIMIT
取得できる情報 値のみ レコード全体
NULLの扱い 自動で無視 先頭に来る場合あり
GROUP BYとの併用 可能 不可
同値の複数行 1つの値のみ 1行のみ
SQL標準 標準SQL RDBMS依存
適した場面 集計・統計 詳細レコード取得

注意:ORDER BY + LIMITはNULLの並び順がRDBMSによって異なります。MySQLとSQL ServerではNULLが先頭、PostgreSQLでは末尾になります。NULLを含む列で使う場合はWHERE salary IS NOT NULLを追加しましょう。

RDBMS別の違い

MIN関数自体はSQL標準ですが、取得件数の制限構文がRDBMSごとに異なります。

RDBMS 最小値レコード取得 NULLの並び順
MySQL ORDER BY col LIMIT 1 先頭(NULLS FIRST)
PostgreSQL ORDER BY col LIMIT 1 末尾(NULLS LAST)
Oracle FETCH FIRST 1 ROW ONLY 末尾(NULLS LAST)
SQL Server TOP 1 先頭(NULLS FIRST)
SQLite ORDER BY col LIMIT 1 先頭(NULLS FIRST)
Oracle / SQL Serverの記述例を見る
Oracle(12c以降)
SELECT * FROM employees
ORDER BY salary ASC NULLS LAST
FETCH FIRST 1 ROW ONLY;
SQL Server
SELECT TOP 1 * FROM employees
WHERE  salary IS NOT NULL
ORDER BY salary ASC;

MINをウィンドウ関数として使う

MIN関数にOVER句を付けると、行を集約せずに各行に最小値を付加できます。

全体の最小給与を各行に表示
SELECT name, department, salary,
       MIN(salary) OVER ()              AS overall_min,
       MIN(salary) OVER (PARTITION BY department) AS dept_min
FROM   employees
WHERE  salary IS NOT NULL;
name department salary overall_min dept_min
田中太郎 営業 350000 280000 280000
佐藤一郎 営業 300000 280000 280000
中村由美 営業 280000 280000 280000
鈴木花子 開発 420000 280000 380000
高橋美咲 開発 380000 280000 380000
渡辺大輔 開発 450000 280000 380000
伊藤恵 人事 320000 280000 320000

OVER ()で全体の最小値、OVER (PARTITION BY department)で部署別の最小値を各行に付加しています。GROUP BYと違い、元のレコードはそのまま保持されます。

最小値との差分を計算する

ウィンドウ関数のMINを使えば、各社員の給与が部署内の最低給与からどれだけ高いかを計算できます。

最小値との差分
SELECT name, department, salary,
       salary - MIN(salary) OVER (PARTITION BY department) AS diff_from_min
FROM   employees
WHERE  salary IS NOT NULL;

CASE WHEN + MIN で条件付き最小値を取得

CASE WHENと組み合わせると、条件に合致するデータだけの最小値を1つのクエリで取得できます。

部署ごとの条件付き最小値

各部署の最小給与を横並びで取得
SELECT
  MIN(CASE WHEN department = '営業' THEN salary END) AS sales_min,
  MIN(CASE WHEN department = '開発' THEN salary END) AS dev_min,
  MIN(CASE WHEN department = '人事' THEN salary END) AS hr_min
FROM employees;
sales_min dev_min hr_min
280000 380000 320000

GROUP BYを使わずに部署別の最小値を1行で横並びに取得できます。クロス集計やレポート作成で便利なテクニックです。

年度ごとの最低給与を横並びで取得

入社年度ごとの最低給与
SELECT
  MIN(CASE WHEN YEAR(hire_date) <= 2020 THEN salary END) AS min_2020,
  MIN(CASE WHEN YEAR(hire_date) >= 2021 THEN salary END) AS min_2021_later
FROM employees;
min_2020 min_2021_later
320000 280000

ポイント:CASE WHENの条件に一致しない行はNULLになり、MINはNULLを無視するため、条件付きの集計が正しく動作します。CASE文の詳しい使い方はこちら

実務でよく使うパターン

シナリオ1:商品テーブルから最安値を取得

カテゴリ別の最安値
SELECT   category,
         MIN(price) AS lowest_price,
         MAX(price) AS highest_price,
         MAX(price) - MIN(price) AS price_range
FROM     products
WHERE    is_active = 1
GROUP BY category;

シナリオ2:注文履歴から初回注文日を取得

顧客別の初回注文日
SELECT   customer_id,
         MIN(order_date) AS first_order,
         MAX(order_date) AS last_order,
         COUNT(*)        AS total_orders
FROM     orders
GROUP BY customer_id;

シナリオ3:ログテーブルから最古のエラー発生日時を取得

エラー種別ごとの初回発生日時
SELECT   error_code,
         MIN(occurred_at) AS first_occurred,
         MAX(occurred_at) AS last_occurred,
         COUNT(*)          AS occurrence_count
FROM     error_logs
WHERE    occurred_at >= '2024-01-01'
GROUP BY error_code
HAVING   COUNT(*) >= 5
ORDER BY first_occurred;

シナリオ4:在庫テーブルから最低在庫数の商品を通知

在庫が全体の最小値と一致する商品
SELECT product_name, stock_quantity, warehouse
FROM   inventory
WHERE  stock_quantity = (
    SELECT MIN(stock_quantity)
    FROM   inventory
    WHERE  stock_quantity > 0
);

シナリオ5:JOINと組み合わせて最安値の仕入先を取得

商品ごとの最安仕入先
SELECT p.product_name, s.supplier_name, ps.unit_price
FROM   product_suppliers ps
JOIN   products p  ON ps.product_id = p.id
JOIN   suppliers s ON ps.supplier_id = s.id
WHERE  ps.unit_price = (
    SELECT MIN(ps2.unit_price)
    FROM   product_suppliers ps2
    WHERE  ps2.product_id = ps.product_id
);

パフォーマンスのコツ

対策 効果 説明
インデックスを作成 大幅に高速化 B-Treeインデックスの最左端を読むだけで完了
WHEREで絞り込み スキャン範囲を削減 不要なデータを事前に除外
GROUP BY列にインデックス グループ化が高速 複合インデックス(group_col, min_col)が最適
不要なJOINを避ける 処理量を削減 MINだけ必要ならサブクエリで取得
インデックス作成例
-- 単一列のMINを高速化
CREATE INDEX idx_salary ON employees (salary);

-- GROUP BY + MINを高速化(複合インデックス)
CREATE INDEX idx_dept_salary ON employees (department, salary);

ポイント:適切なインデックスがあれば、MIN関数はテーブル全体をスキャンせずにインデックスの先頭1件を読むだけで完了します(Index Only Scan)。大量データでもミリ秒単位で結果を返せます。

よくあるエラーと対処法

エラー 原因 対処法
not a single-group group function GROUP BY なしで集約関数と非集約列を同時にSELECT GROUP BY を追加 or サブクエリに変更
aggregate functions are not allowed in WHERE WHERE句でMIN関数を使用 HAVING句に変更 or サブクエリを使用
結果がNULL 対象行が0件 or 全てNULL COALESCEでデフォルト値を設定
文字列の並び順が想定外 照合順序の違い COLLATEを明示的に指定

エラー例と修正

NG: GROUP BY なしで非集約列をSELECT
-- エラーになる
SELECT name, MIN(salary)
FROM   employees;
OK: サブクエリで解決
-- サブクエリを使って正しく取得
SELECT name, salary
FROM   employees
WHERE  salary = (SELECT MIN(salary) FROM employees);
NG: WHERE句でMIN関数を使用
-- エラーになる
SELECT   department, MIN(salary)
FROM     employees
WHERE    MIN(salary) >= 300000
GROUP BY department;
OK: HAVING句に変更
-- HAVING句を使えばOK
SELECT   department, MIN(salary)
FROM     employees
GROUP BY department
HAVING   MIN(salary) >= 300000;

集約関数まとめ

MIN関数は5つの集約関数のうちの1つです。それぞれの違いを確認しましょう。

関数 機能 NULLの扱い 対応型 記事リンク
MIN 最小値 無視 数値・日付・文字列 この記事
MAX 最大値 無視 数値・日付・文字列 MAX関数
SUM 合計 無視 数値のみ SUM関数
AVG 平均値 無視 数値のみ AVG関数
COUNT 件数 COUNT(*)は含む
COUNT(列)は無視
全型 COUNT関数

まとめ

項目 内容
基本構文 SELECT MIN(列名) FROM テーブル名
対応データ型 数値・日付・文字列すべて
NULLの扱い 自動的に無視(全てNULL or 0件でNULLを返す)
グループ別取得 GROUP BY句と併用
集計結果の絞り込み HAVING句(WHERE句ではエラー)
レコード全体の取得 サブクエリ / ORDER BY + LIMIT / ウィンドウ関数
ウィンドウ関数 MIN() OVER () で行を集約せずに最小値を付加
高速化 対象列にインデックスを作成

MIN関数はシンプルな関数ですが、サブクエリやウィンドウ関数と組み合わせることで実務の多くの場面で活躍します。まずは基本のGROUP BYとの組み合わせを押さえ、NULLの挙動を理解しておけば、困ることはありません。

関連記事