【SQL】平均値が大きい順にデータをソートする方法|AVG + GROUP BY + ORDER BYの使い方と実務パターン

SQLで部署ごとの平均給与カテゴリ別の平均スコアを求め、大きい順に並べ替えたいケースは実務で頻出します。この処理には AVG + GROUP BY + ORDER BY DESC の組み合わせが基本です。

この記事では、基本構文からWHERE / HAVING による絞り込みTOP N の取得JOIN との組み合わせサブクエリウィンドウ関数RDBMS別の違いまで、平均値ソートに関する知識を体系的に解説します。

この記事で学べること

  • AVG + GROUP BY + ORDER BY DESC の基本構文と使い方
  • WHERE句で集計前にデータを絞り込む方法
  • HAVING句で集計後に平均値をフィルタする方法
  • TOP N / LIMIT で上位N件だけ取得する方法
  • JOINと組み合わせて関連テーブルの情報を付加する方法
  • サブクエリで平均が最大の部署の全行を取得する方法
  • ウィンドウ関数 AVG() OVER(PARTITION BY) の使い方
  • MySQL・PostgreSQL・SQL Server・OracleのRDBMS別の違い
  • よくあるエラーと対処法
スポンサーリンク

サンプルデータ

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

id name department salary hire_date
1 田中太郎 営業 350,000 2020-04-01
2 鈴木花子 開発 420,000 2019-07-15
3 佐藤一郎 営業 300,000 2021-01-10
4 高橋美咲 開発 380,000 2022-03-20
5 山田健太 人事 280,000 2023-06-01
6 伊藤恵 人事 320,000 2020-09-15
7 渡辺大輔 開発 450,000 2018-11-01
8 中村由美 営業 280,000 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, '山田健太', '人事', 280000, '2023-06-01'),
(6, '伊藤恵',   '人事', 320000, '2020-09-15'),
(7, '渡辺大輔', '開発', 450000, '2018-11-01'),
(8, '中村由美', '営業', 280000, '2023-02-14');

AVG + GROUP BY + ORDER BY DESC の基本構文

グループごとの平均値を求めて降順に並べるには、AVGGROUP BYORDER BY DESC の3つを組み合わせます。

基本構文

基本構文
SELECT グループ列, AVG(数値列) AS 別名
FROM テーブル名
GROUP BY グループ列
ORDER BY 別名 DESC;

ポイント:ORDER BY DESC を指定しないとソート順は不定です。平均値が大きい順に並べるには必ず DESC を付けましょう。

部署ごとの平均給与を高い順に取得

部署別の平均給与(降順)
SELECT
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;

実行結果

department | avg_salary
-----------+-----------
開発       | 416666.67
営業       | 310000.00
人事       | 300000.00

開発部の平均給与が最も高く、次いで営業部、人事部の順になっています。AVG は自動的に算術平均(合計 / 件数)を計算します。

WHERE句で集計前にデータを絞り込む

WHERE 句を使うと、集計の対象になるデータを事前にフィルタできます。集計関数(AVG)が実行される前に行が絞り込まれるため、条件に合致したデータだけで平均が計算されます。

2021年以降に入社した社員のみで集計

WHERE句で入社日を絞り込み
SELECT
  department,
  AVG(salary) AS avg_salary,
  COUNT(*) AS cnt
FROM employees
WHERE hire_date >= '2021-01-01'
GROUP BY department
ORDER BY avg_salary DESC;

実行結果

department | avg_salary | cnt
-----------+------------+----
開発       | 380000.00  | 1
営業       | 290000.00  | 2
人事       | 280000.00  | 1

2021年以降入社の社員のみで平均を計算しています。佐藤一郎(営業・300,000)、高橋美咲(開発・380,000)、山田健太(人事・280,000)、中村由美(営業・280,000)の4名が対象です。

注意:WHERE句では AVG(salary) >= 300000 のような集計関数は使えません。集計結果で絞り込むには次のセクションで紹介する HAVING 句を使います。

HAVING句で集計後にフィルタする

HAVING 句は GROUP BY で集計した後の結果に対して条件を指定します。「平均給与が30万円以上の部署だけ取得」といった使い方ができます。

平均給与が30万円以上の部署のみ取得

HAVING句で平均給与をフィルタ
SELECT
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) >= 300000
ORDER BY avg_salary DESC;

実行結果

department | avg_salary
-----------+-----------
開発       | 416666.67
営業       | 310000.00

人事部の平均給与は300,000なので >= 300000 の条件ぎりぎりですが、ここでは >= なので含まれるはずです。ただし、上記例では人事部の平均は (280000 + 320000) / 2 = 300000 なのでちょうど30万円です。>(より大きい)にすると人事部は除外されます。

WHERE + HAVING を組み合わせる

WHEREHAVING は同時に使えます。WHERE で行を絞り込み → GROUP BY で集計 → HAVING で集計結果を絞り込みという順番で処理されます。

WHERE + HAVING の組み合わせ
SELECT
  department,
  AVG(salary) AS avg_salary,
  COUNT(*) AS cnt
FROM employees
WHERE salary >= 300000
GROUP BY department
HAVING COUNT(*) >= 2
ORDER BY avg_salary DESC;

実行結果

department | avg_salary | cnt
-----------+------------+----
開発       | 416666.67  | 3
営業       | 325000.00  | 2

まず WHERE salary >= 300000 で給与30万円未満の行を除外し、そのうえで HAVING COUNT(*) >= 2 で2人以上いる部署だけ残しています。

TOP N / LIMIT で上位N件を取得

平均給与が高い上位3部署だけ取得したいといった場合、LIMIT(MySQL / PostgreSQL)や TOP(SQL Server)を使います。

MySQL / PostgreSQL:LIMIT

MySQL / PostgreSQL
SELECT
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC
LIMIT 3;

SQL Server:TOP

SQL Server
SELECT TOP 3
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;

Oracle:FETCH FIRST

Oracle 12c以降
SELECT
  department,
  AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC
FETCH FIRST 3 ROWS ONLY;

実行結果(共通)

department | avg_salary
-----------+-----------
開発       | 416666.67
営業       | 310000.00
人事       | 300000.00

今回のサンプルデータは3部署しかないため全件が返りますが、部署数が多い場合に効果を発揮します。

AVG + JOIN で関連テーブルと結合する

実務では部署名が別テーブル(departments)に格納されているケースが一般的です。JOIN と組み合わせて、部署の追加情報を含めた集計結果を取得できます。

departments テーブル

dept_code dept_name location
営業 営業部 東京
開発 開発部 大阪
人事 人事部 東京
JOINで部署情報を付加して平均給与ソート
SELECT
  d.dept_name,
  d.location,
  AVG(e.salary) AS avg_salary,
  COUNT(*) AS emp_count
FROM employees e
INNER JOIN departments d
  ON e.department = d.dept_code
GROUP BY d.dept_name, d.location
ORDER BY avg_salary DESC;

実行結果

dept_name | location | avg_salary | emp_count
----------+----------+------------+----------
開発部    | 大阪     | 416666.67  | 3
営業部    | 東京     | 310000.00  | 3
人事部    | 東京     | 300000.00  | 2

JOIN によって部署の正式名称と所在地を併せて表示できます。GROUP BY には SELECT に含む非集計列をすべて列挙する必要があります。

サブクエリで平均が最大の部署の全行を取得

「平均給与が最も高い部署に所属する社員の全レコードを取得したい」という場合、サブクエリを使います。

方法1: IN + サブクエリ

平均給与が最大の部署の全社員を取得
SELECT *
FROM employees
WHERE department IN (
  SELECT department
  FROM employees
  GROUP BY department
  ORDER BY AVG(salary) DESC
  LIMIT 1
);

実行結果

id | name     | department | salary | hire_date
---+----------+------------+--------+-----------
2  | 鈴木花子 | 開発       | 420000 | 2019-07-15
4  | 高橋美咲 | 開発       | 380000 | 2022-03-20
7  | 渡辺大輔 | 開発       | 450000 | 2018-11-01

方法2: ALL演算子を使う方法

LIMIT が使えないRDBMSや、より汎用的な書き方として ALL 演算子を使う方法もあります。

ALL演算子で最大平均の部署を取得
SELECT *
FROM employees
WHERE department IN (
  SELECT department
  FROM employees
  GROUP BY department
  HAVING AVG(salary) >= ALL (
    SELECT AVG(salary)
    FROM employees
    GROUP BY department
  )
);

この書き方は LIMIT / TOP に依存しないため、全てのRDBMSで動作します。

ウィンドウ関数 AVG() OVER(PARTITION BY) で行ごとに平均を付加

ウィンドウ関数を使うと、元の行を維持したまま各グループの平均値を列として付加できます。GROUP BY のように行が集約されないのが特徴です。

各社員に部署平均を付加して並べ替え

ウィンドウ関数で部署平均を付加
SELECT
  name,
  department,
  salary,
  AVG(salary) OVER(PARTITION BY department) AS dept_avg
FROM employees
ORDER BY dept_avg DESC, salary DESC;

実行結果

name     | department | salary | dept_avg
---------+------------+--------+-----------
渡辺大輔 | 開発       | 450000 | 416666.67
鈴木花子 | 開発       | 420000 | 416666.67
高橋美咲 | 開発       | 380000 | 416666.67
田中太郎 | 営業       | 350000 | 310000.00
佐藤一郎 | 営業       | 300000 | 310000.00
中村由美 | 営業       | 280000 | 310000.00
伊藤恵   | 人事       | 320000 | 300000.00
山田健太 | 人事       | 280000 | 300000.00

各行に dept_avg 列が付加され、部署平均が高い順に全社員が並んでいます。同じ部署内では個人の給与が高い順です。

ウィンドウ関数で順位を付ける

RANK() を組み合わせると、部署平均の順位も付加できます。

DENSE_RANKで部署平均の順位を付加
SELECT
  name,
  department,
  salary,
  AVG(salary) OVER(PARTITION BY department) AS dept_avg,
  DENSE_RANK() OVER(
    ORDER BY AVG(salary) OVER(PARTITION BY department) DESC
  ) AS dept_rank
FROM employees
ORDER BY dept_rank, salary DESC;

注意:上記のネストした OVER 句は一部のRDBMS(MySQLなど)ではエラーになります。その場合はサブクエリで先にウィンドウ関数を計算し、外側のクエリで DENSE_RANK を適用してください。

サブクエリで安全に順位を付ける(全RDBMS対応)

サブクエリ + DENSE_RANK(全RDBMS対応)
SELECT
  name,
  department,
  salary,
  dept_avg,
  DENSE_RANK() OVER(ORDER BY dept_avg DESC) AS dept_rank
FROM (
  SELECT
    name,
    department,
    salary,
    AVG(salary) OVER(PARTITION BY department) AS dept_avg
  FROM employees
) sub
ORDER BY dept_rank, salary DESC;

RDBMS別の違い

基本構文はほぼ共通ですが、件数制限やウィンドウ関数の対応状況に違いがあります。

RDBMS 上位N件 ウィンドウ関数 備考
MySQL LIMIT N 8.0以降で対応 5.7以前はウィンドウ関数不可
PostgreSQL LIMIT N 全バージョンで対応 FETCH FIRST も利用可
SQL Server TOP N 2005以降で対応 OFFSET-FETCH も利用可(2012以降)
Oracle FETCH FIRST N ROWS ONLY 全バージョンで対応 12c未満は ROWNUM を使用

Oracle 11g以前の上位N件取得

Oracle 12c未満では FETCH FIRST が使えないため、ROWNUM を使った書き方が必要です。

Oracle 11g以前
SELECT * FROM (
  SELECT
    department,
    AVG(salary) AS avg_salary
  FROM employees
  GROUP BY department
  ORDER BY avg_salary DESC
)
WHERE ROWNUM <= 3;

よくあるエラーと対処法

AVG + GROUP BY + ORDER BY を使う際に発生しやすいエラーをまとめました。

エラー内容 原因 対処法
WHERE句でAVGを使用 WHERE句では集計関数を使えない HAVING 句に変更する
SELECT句に非集計列 GROUP BYに含まれない列をSELECTに記述 GROUP BYに追加するか、集計関数で囲む
ORDER BY DESC の書き忘れ DESCを付けないとASC(昇順)になる ORDER BY avg_salary DESC と明示する
HAVINGでエイリアスを使用 一部のRDBMSではHAVINGでエイリアス不可 HAVING AVG(salary) と関数を直接書く
NULLで意図しない平均値 AVGはNULL行を除外して計算する COALESCE(salary, 0) でNULLを0に変換
LIMITがエラー(SQL Server/Oracle) RDBMSによって件数制限の構文が異なる TOP N(SQL Server)、FETCH FIRST(Oracle)を使う

エラー例:WHERE句でAVGを使ったケース

NGな書き方
-- NG: WHERE句で集計関数は使えない
SELECT department, AVG(salary)
FROM employees
WHERE AVG(salary) > 300000  -- エラー!
GROUP BY department;
OKな書き方
-- OK: HAVING句を使う
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 300000
ORDER BY avg_salary DESC;

まとめ

この記事で紹介した、平均値を大きい順にソートするための主要な手法をまとめます。

手法 用途 構文のポイント
AVG + GROUP BY + ORDER BY DESC 基本のグループ平均ソート DESCを忘れずに指定する
WHERE句 集計前のデータ絞り込み 集計関数は使用不可
HAVING句 集計後の結果フィルタ AVG(salary) >= 値 の形で記述
LIMIT / TOP / FETCH FIRST 上位N件の取得 RDBMSによって構文が異なる
JOIN + AVG 関連テーブルの情報付加 GROUP BYに非集計列を全て含める
サブクエリ 平均最大の部署の全行取得 IN + サブクエリ または ALL演算子
AVG() OVER(PARTITION BY) 行を維持したまま平均を付加 GROUP BYなしで各行に平均を追加

実務のコツ:まずは AVG + GROUP BY + ORDER BY DESC の基本形を押さえ、必要に応じて WHERE / HAVING / LIMIT で条件を追加していくのがおすすめです。ウィンドウ関数は「元の行も残したい」場面で活用しましょう。

関連記事