SQLのAVG関数は、指定した列の平均値を計算する集計関数です。売上データの分析、成績の平均点算出、統計レポートなど、データベースを使った集計処理で頻繁に使われます。
この記事では、AVG関数の基本構文からNULL の扱い、GROUP BY との組み合わせ、条件付き平均、ウィンドウ関数による移動平均まで、実務で必要な知識を体系的に解説します。
? この記事で学べること
・ AVG関数の基本構文と使い方
・ NULL値が平均計算に与える影響と対処法
・ DISTINCT で重複を除外した平均の求め方
・ GROUP BY でグループごとの平均を算出する方法
・ HAVING で平均値に条件を付けてフィルタする方法
・ CASE WHEN を使った条件付き平均
・ サブクエリで「平均以上」のデータを抽出する方法
・ ROUND / CAST で小数点を制御する方法
・ ウィンドウ関数 AVG() OVER で移動平均を計算する方法
AVG関数の基本構文
AVG関数は、指定した列の数値データの算術平均(合計 ÷ 件数)を返します。
SQL – AVG基本構文
SELECT AVG(列名) AS 別名
FROM テーブル名;
具体的な例で確認しましょう。以下のような employees テーブルがあるとします。
| id |
name |
department |
salary |
| 1 |
田中 |
営業 |
350000 |
| 2 |
佐藤 |
開発 |
420000 |
| 3 |
鈴木 |
営業 |
380000 |
| 4 |
高橋 |
開発 |
450000 |
| 5 |
伊藤 |
総務 |
330000 |
全社員の給与の平均を求めるには、次のように書きます。
SQL – 全社員の平均給与
SELECT AVG(salary) AS avg_salary
FROM employees;
▼ 実行結果
avg_salary
----------
386000
(350000 + 420000 + 380000 + 450000 + 330000)÷ 5 = 386000 が返されます。
AVG関数とNULLの関係
AVG関数を使う上で最も重要なポイントが、NULL値の扱いです。AVG関数はNULLの行を自動的に除外して平均を計算します。
| id |
name |
score |
| 1 |
田中 |
80 |
| 2 |
佐藤 |
60 |
| 3 |
鈴木 |
NULL |
| 4 |
高橋 |
100 |
SQL – NULLを含むAVG
-- NULLを除外して計算される(80 + 60 + 100)÷ 3 = 80
SELECT AVG(score) AS avg_score
FROM test_scores;
▼ 実行結果
avg_score
---------
80
鈴木さんのスコアがNULLなので、3件(80 + 60 + 100)で割り算されます。4件で割りたい場合は、NULLを0に変換する必要があります。
NULLを0として計算する方法
NULLを0に置換して全行を母数に含めたい場合は、COALESCE関数またはIFNULL関数(MySQL)を使います。
SQL – NULLを0に変換して平均を計算
-- 方法1: COALESCE(全RDBMS共通)
SELECT AVG(COALESCE(score, 0)) AS avg_score
FROM test_scores;
-- 方法2: IFNULL(MySQL専用)
SELECT AVG(IFNULL(score, 0)) AS avg_score
FROM test_scores;
-- 方法3: NVL(Oracle専用)
SELECT AVG(NVL(score, 0)) AS avg_score
FROM test_scores;
▼ 実行結果(すべて同じ)
avg_score
---------
60
(80 + 60 + 0 + 100)÷ 4 = 60 となります。
注意:NULL変換の使い分け
| 関数 |
対応RDBMS |
特徴 |
| COALESCE |
全RDBMS |
SQL標準。複数の代替値を指定可能 |
| IFNULL |
MySQL / SQLite |
引数は2つのみ |
| NVL |
Oracle |
Oracle独自。引数は2つのみ |
移植性を考えると、COALESCE を使うのがベストプラクティスです。NULLの検索方法やNULLでないデータの抽出方法も併せて確認してください。
AVG関数でDISTINCTを使う
DISTINCT キーワードを指定すると、重複する値を1つにまとめてから平均を計算します。
SQL – DISTINCTの有無による違い
-- データ: salary = 300000, 300000, 400000, 500000
-- ALL(デフォルト): (300000+300000+400000+500000) ÷ 4 = 375000
SELECT AVG(ALL salary) AS avg_all
FROM employees;
-- DISTINCT: (300000+400000+500000) ÷ 3 = 400000
SELECT AVG(DISTINCT salary) AS avg_distinct
FROM employees;
? ALLとDISTINCTの違い
ALL(デフォルト): 重複値もすべて含めて計算
DISTINCT: 重複値を除外してユニークな値のみで計算
DISTINCTは「何種類の給与水準があるか」を分析する場合などに有用です。通常の平均計算では指定不要です。
GROUP BYでグループごとの平均を求める
AVG関数とGROUP BYを組み合わせることで、カテゴリごとの平均を求められます。実務で最も頻繁に使うパターンです。
SQL – 部署ごとの平均給与
SELECT
department,
AVG(salary) AS avg_salary,
COUNT(*) AS emp_count
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;
▼ 実行結果
department | avg_salary | emp_count
-----------+------------+----------
開発 | 435000 | 2
営業 | 365000 | 2
総務 | 330000 | 1
GROUP BYを使った集計の詳しい解説は「GROUP BY + SUM の基本からROLLUP・ウィンドウ関数まで」をご覧ください。GROUP BYで件数をカウントする方法も参考になります。
複数列でグループ化する
GROUP BYに複数の列を指定すれば、より細かい粒度で平均を算出できます。
SQL – 部署×役職ごとの平均
SELECT
department,
position,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department, position
ORDER BY department, avg_salary DESC;
HAVING句で平均値に条件をつける
GROUP BYで集計した後に条件を付けるにはHAVING句を使います。WHERE句は集計前のフィルタなので、AVG関数の結果に条件を付けることはできません。
SQL – 平均給与が40万以上の部署
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) >= 400000;
? WHERE と HAVING の違い
| 項目 |
WHERE |
HAVING |
| 実行タイミング |
GROUP BY の前 |
GROUP BY の後 |
| 集計関数 |
使用不可 |
使用可能 |
| 用途 |
行レベルの絞り込み |
グループレベルの絞り込み |
HAVING句の詳しい使い方は「HAVING句でグループ化されたデータに条件を適用する方法」を参照してください。
サブクエリで「平均以上」のデータを取得する
「平均以上」や「平均以下」のレコードを抽出するには、サブクエリでAVGの結果をWHERE句に渡します。
SQL – 平均給与以上の社員を抽出
-- 平均以上のデータを取得
SELECT name, department, salary
FROM employees
WHERE salary >= (
SELECT AVG(salary) FROM employees
);
▼ 実行結果(平均 386000 以上)
name | department | salary
-------+------------+--------
佐藤 | 開発 | 420000
高橋 | 開発 | 450000
グループごとの平均と比較する(相関サブクエリ)
「自分の部署の平均給与より高い社員」のように、グループごとの平均と比較するには相関サブクエリを使います。
SQL – 部署平均より高い社員
SELECT e1.name, e1.department, e1.salary
FROM employees e1
WHERE e1.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e1.department
);
データ量が多い場合、相関サブクエリはパフォーマンスが低下することがあります。後述するウィンドウ関数で同等の処理をより効率的に書けます。
CASE WHEN で条件付き平均を求める
AVG関数の中でCASE WHENを使うと、1つのクエリで条件別の平均を同時に算出できます。
SQL – 科目別の平均点を1行で算出
SELECT
student_name,
AVG(CASE WHEN subject IN ('国語', '英語', '社会')
THEN score END
) AS liberal_arts_avg,
AVG(CASE WHEN subject IN ('数学', '理科')
THEN score END
) AS science_avg
FROM exam_results
GROUP BY student_name;
CASE WHEN で条件に該当しない行はNULLを返すため、AVG関数が自動的に除外してくれるのがポイントです。テーブルの結合なしで、1回のスキャンで複数の条件別平均を取得できます。
CASE文の詳しい使い方は「CASE文を使用した複数条件でのデータ更新方法」や「CASE文とDECODE関数の違いと使い分け」も参考になります。
ROUNDとCASTで小数点を制御する
AVG関数の結果は小数点以下が長くなることがあります。ROUND関数で四捨五入し、見やすい値にしましょう。
SQL – 小数点以下の制御
-- 小数点以下2桁に四捨五入
SELECT ROUND(AVG(salary), 2) AS avg_salary
FROM employees;
-- 小数点以下を切り捨てて整数にする
SELECT ROUND(AVG(salary), 0) AS avg_salary
FROM employees;
整数列の平均で小数点以下を表示する
整数型(INT)の列にAVGを使うと、RDBMSによっては小数点以下が切り捨てられる場合があります(SQL Serverなど)。この場合はCASTで型変換してから計算します。
SQL – 整数列の平均を小数で取得
-- SQL Server: INT列のAVGは整数になるためCASTが必要
SELECT AVG(CAST(score AS DECIMAL(5,2))) AS avg_score
FROM test_scores;
-- ROUNDとCASTを組み合わせる
SELECT
department,
ROUND(AVG(CAST(salary AS DECIMAL(10,2))), 1) AS avg_salary
FROM employees
GROUP BY department;
? RDBMS別の整数AVG動作
| RDBMS |
INT列のAVG結果 |
CASTの必要性 |
| MySQL |
小数(DOUBLE型) |
不要 |
| PostgreSQL |
小数(NUMERIC型) |
不要 |
| Oracle |
小数(NUMBER型) |
不要 |
| SQL Server |
整数(INT型) |
必要 |
ウィンドウ関数 AVG() OVER で行ごとの平均を計算する
通常のAVG + GROUP BYは結果を1行にまとめますが、ウィンドウ関数のAVG() OVERを使えば、元の行を保持したまま平均値を追加できます。
SQL – ウィンドウ関数で部署平均を横に並べる
SELECT
name,
department,
salary,
AVG(salary) OVER () AS company_avg,
AVG(salary) OVER (
PARTITION BY department
) AS dept_avg,
salary - AVG(salary) OVER (
PARTITION BY department
) AS diff_from_dept_avg
FROM employees;
▼ 実行結果
name | dept | salary | company_avg | dept_avg | diff
-------+--------+--------+-------------+----------+------
田中 | 営業 | 350000 | 386000 | 365000 | -15000
鈴木 | 営業 | 380000 | 386000 | 365000 | 15000
佐藤 | 開発 | 420000 | 386000 | 435000 | -15000
高橋 | 開発 | 450000 | 386000 | 435000 | 15000
伊藤 | 総務 | 330000 | 386000 | 330000 | 0
ウィンドウ関数を使えば、相関サブクエリを使わずに各社員の給与と部署平均の差を1クエリで算出できます。パフォーマンスも大幅に向上します。
移動平均(Moving Average)を計算する
時系列データで「直近N日間の平均」を求めるには、ROWS BETWEEN を使って移動平均を計算します。売上トレンドの分析に非常に有用です。
SQL – 7日間移動平均
SELECT
sale_date,
daily_amount,
ROUND(
AVG(daily_amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
), 0
) AS moving_avg_7d
FROM daily_sales
ORDER BY sale_date;
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW は「現在の行を含む直近7行」を指定しています。初期の行(7行未満の期間)は、利用可能な行のみで平均が計算されます。
他の集計関数との組み合わせ
実務では、AVGを他の集計関数と組み合わせて多角的にデータを分析します。
SQL – 統計サマリーレポート
SELECT
department,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
MAX(salary) - MIN(salary) AS salary_range,
SUM(salary) AS total_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;
各集計関数の詳細は以下の記事で解説しています。
JOINと組み合わせて使う
複数テーブルを結合してAVGを使う場合、重複に注意が必要です。1対多の結合では、行が増幅されて平均値がずれることがあります。
SQL – JOINでの平均計算(重複回避)
-- ❌ 悪い例: 1対多JOINで平均がずれる
SELECT AVG(e.salary) AS avg_salary
FROM employees e
JOIN orders o ON e.id = o.employee_id;
-- → 注文が多い社員の給与が重複カウントされる
-- ✅ 良い例: サブクエリで事前に集計
SELECT AVG(e.salary) AS avg_salary
FROM employees e
WHERE e.id IN (
SELECT DISTINCT employee_id FROM orders
);
テーブル結合の基本は「INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOINの使い方」で確認できます。
実務でよく使うパターン集
月別の平均売上を算出する
SQL – 月別平均売上
-- MySQL
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
ROUND(AVG(amount), 0) AS avg_amount,
COUNT(*) AS order_count
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
年齢層別の平均を計算する
SQL – 年齢層別の平均給与
SELECT
CASE
WHEN age < 30 THEN '20代以下'
WHEN age < 40 THEN '30代'
WHEN age < 50 THEN '40代'
ELSE '50代以上'
END AS age_group,
ROUND(AVG(salary), 0) AS avg_salary,
COUNT(*) AS emp_count
FROM employees
GROUP BY age_group
ORDER BY avg_salary;
全体平均からの乖離率を計算する
SQL – 平均からの乖離率
SELECT
name,
salary,
AVG(salary) OVER () AS avg_salary,
ROUND(
(salary - AVG(salary) OVER ())
/ AVG(salary) OVER () * 100,
1) AS deviation_pct
FROM employees
ORDER BY deviation_pct DESC;
パフォーマンスの注意点
⚡ AVG関数の最適化ポイント
- WHEREで先に絞り込む: AVGの計算対象を減らすことで高速化
- インデックスを活用: GROUP BY列やWHERE条件列にインデックスがあると効率的
- 相関サブクエリを避ける: ウィンドウ関数 AVG() OVER で代替すると高速
- 不要な列を含めない: SELECT * ではなく必要な列だけを指定
- 大量データは事前集計: 日次集計テーブルを作成し、そこからAVGする
SQL実行順序とAVG関数の位置
AVG関数がどのタイミングで実行されるかを理解すると、WHEREとHAVINGの使い分けが明確になります。
① FROM
→
② WHERE
→
③ GROUP BY
→
④ AVG() 計算
→
⑤ HAVING
→
⑥ SELECT
→
⑦ ORDER BY
AVGは③ GROUP BY の後に計算されるため、WHERE句(②)では使えませんが、HAVING句(⑤)では使えます。SELECT文でデータを取得する基本については「SELECT文でデータを抽出する方法」をご覧ください。
AVG関数の計算原理: SUM ÷ COUNT
AVG関数の内部動作は、SUM(列) ÷ COUNT(列) と同等です。この原理を知っておくと、NULLの挙動やDISTINCTの動作が直感的に理解できます。
AVG(salary)
=
SUM(salary)
÷
COUNT(salary)
※ COUNT(salary) は NULLを除外してカウント
※ COUNT(*) とは異なる点に注意
SQL – AVGをSUM/COUNTで再現
-- この2つは同じ結果を返す
SELECT AVG(salary) AS avg1
FROM employees;
SELECT SUM(salary) / COUNT(salary) AS avg2
FROM employees;
-- ※ COUNT(*) ではなく COUNT(salary) を使うこと
この原理を応用すると、加重平均(重み付き平均)も計算できます。
SQL – 加重平均(単価×数量の平均単価)
-- 単純平均: 各商品の単価を均等に扱う
SELECT AVG(unit_price) AS simple_avg
FROM order_details;
-- 加重平均: 販売数量で重み付け
SELECT
SUM(unit_price * quantity) / SUM(quantity) AS weighted_avg
FROM order_details;
エッジケースとよくあるエラー
AVG関数を使う際に遭遇しやすい問題と、その対処法をまとめます。
全レコードがNULL / 0件の場合
対象データが0件の場合や、全行がNULLの場合、AVG関数はNULLを返します。0ではないので注意してください。
SQL – 0件時のAVGはNULL
-- 該当データなし → NULLが返る(0ではない)
SELECT AVG(salary) FROM employees WHERE 1 = 0;
-- 結果: NULL
-- NULLを0に変換したい場合はCOALESCEで囲む
SELECT COALESCE(AVG(salary), 0) AS avg_salary
FROM employees
WHERE department = '存在しない部署';
-- 結果: 0
文字列カラムにAVGを使った場合
⚠️ 文字列カラムへのAVG適用
| RDBMS |
動作 |
| MySQL |
暗黙的に数値変換を試み、変換できない場合は0として扱う(警告あり) |
| PostgreSQL |
エラー: function avg(character varying) does not exist |
| Oracle |
エラー: ORA-01722: invalid number |
| SQL Server |
エラー: Operand data type varchar is invalid for avg operator |
AVG関数は数値型の列にのみ使用してください。文字列として格納された数値にはCASTで型変換してから使います。
WHEREでAVGを使うとエラーになる
SQL – WHERE句ではAVGは使えない
-- ❌ エラー: WHERE句で集計関数は使えない
SELECT * FROM employees
WHERE salary > AVG(salary);
-- ✅ サブクエリを使う
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- ✅ ウィンドウ関数を使う
SELECT * FROM (
SELECT *, AVG(salary) OVER() AS avg_sal
FROM employees
) t
WHERE salary > avg_sal;
平均値の落とし穴: 外れ値と中央値
AVG関数は外れ値(極端に大きい/小さい値)の影響を受けやすい特性があります。データの分布が偏っている場合、平均値だけでは実態を正しく把握できません。
SQL – 外れ値の影響
-- salary: 300000, 320000, 350000, 330000, 5000000(役員)
-- 平均: 1,260,000 ← 実態とかけ離れている
-- 対策1: 外れ値を除外して平均を計算
SELECT AVG(salary) AS trimmed_avg
FROM employees
WHERE salary < (SELECT AVG(salary) * 3 FROM employees);
-- 対策2: 中央値(MEDIAN)を使う
-- PostgreSQL
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees;
-- Oracle
SELECT MEDIAN(salary) AS median_salary
FROM employees;
? 平均値 vs 中央値 の使い分け
| 指標 |
適している場面 |
注意点 |
| 平均値(AVG) |
正規分布に近いデータ、合計値が重要な場合 |
外れ値に弱い |
| 中央値(MEDIAN) |
給与・価格など偏りやすいデータ |
外れ値に強い |
まとめ
AVG関数 まとめ
| パターン |
構文 |
用途 |
| 基本 |
AVG(列名) |
全体の平均値を取得 |
| NULL対策 |
AVG(COALESCE(列, 0)) |
NULL行も母数に含める |
| 重複除外 |
AVG(DISTINCT 列名) |
ユニークな値のみで平均 |
| グループ別 |
AVG(列) … GROUP BY |
カテゴリ別の平均 |
| 条件フィルタ |
HAVING AVG(列) >= N |
平均値で絞り込み |
| 条件付き |
AVG(CASE WHEN … THEN 列 END) |
条件別の平均を同時算出 |
| 行ごと |
AVG(列) OVER (PARTITION BY …) |
行を保持したまま平均表示 |
| 移動平均 |
AVG(列) OVER (ORDER BY … ROWS …) |
トレンド分析 |
| 小数制御 |
ROUND(AVG(CAST(列 AS DECIMAL)), N) |
桁数を指定した平均 |
AVG関数は単体で使うだけでなく、GROUP BY、HAVING、CASE WHEN、ウィンドウ関数と組み合わせることで、高度なデータ分析が可能になります。まずは基本構文とNULLの扱いを押さえたうえで、実務でよく使うパターンを一つずつ試してみてください。