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 の基本構文
グループごとの平均値を求めて降順に並べるには、AVG・GROUP BY・ORDER 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 を組み合わせる
WHERE と HAVING は同時に使えます。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 で条件を追加していくのがおすすめです。ウィンドウ関数は「元の行も残したい」場面で活用しましょう。
関連記事