【SQL】AVG関数を使用して平均を求める方法|NULL対策・GROUP BY・ウィンドウ関数まで完全解説

【SQL】AVG関数を使用して平均を求める方法|NULL対策・GROUP BY・ウィンドウ関数まで完全解説 SQL

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) 給与・価格など偏りやすいデータ 外れ値に強い

よくある質問(FAQ)

Q. AVG関数でNULLはどう扱われますか?
A. AVGはNULLを無視して計算します(分子・分母ともにNULLは除外)。全行を分母にしたい場合はSUM(col) / COUNT(*)を使います。NULLを0として平均を出したい場合はAVG(COALESCE(col, 0))のようにCOALESCEで変換してから使います。
Q. グループ別の平均を求め、一定以上の平均値のグループだけ抽出するにはどうすればよいですか?
A. GROUP BYとHAVING句を組み合わせます:SELECT dept, AVG(salary) FROM employees GROUP BY dept HAVING AVG(salary) >= 500000。WHERE句は集計前のフィルタ、HAVING句は集計後のフィルタです。
Q. 平均値とともに各行の平均からの乖離を表示するにはどうすればよいですか?
A. ウィンドウ関数を使います:SELECT name, salary, AVG(salary) OVER() AS avg_all, salary - AVG(salary) OVER() AS diff FROM employees。OVER()は全体平均、OVER(PARTITION BY dept)はグループ別平均を同時に取得できます。

まとめ

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の扱いを押さえたうえで、実務でよく使うパターンを一つずつ試してみてください。