「SQLで中央値を求めたいけど、どう書けばいいの?」「AVG関数は使えるのに、中央値の関数が見つからない…」と困っていませんか?
中央値(メディアン)は、データを小さい順に並べたときに真ん中に位置する値のことです。平均値と違って、極端な値(外れ値)の影響を受けにくいため、給与や年齢、価格などの統計分析で重要な指標となります。
この記事では、MySQL、PostgreSQL、SQL Serverなど、主要なデータベースでの中央値の求め方を分かりやすく解説していきます。
中央値とは
中央値の基本
中央値とは、データを小さい順に並べたときに、ちょうど真ん中に位置する値のことです。
例えば、以下の5つの数値があるとします。
1, 3, 5, 7, 9
この場合、真ん中の値は「5」なので、中央値は5です。
データ数が偶数の場合
データ数が偶数の場合は、真ん中の2つの値の平均を取ります。
1, 3, 5, 7, 9, 11
真ん中は「5」と「7」なので、中央値は(5 + 7) ÷ 2 = 6です。
平均値との違い
平均値と中央値は、どちらもデータの代表値ですが、大きな違いがあります。
例:社員の月給データ
20万円, 22万円, 25万円, 28万円, 30万円, 500万円
- 平均値:(20 + 22 + 25 + 28 + 30 + 500) ÷ 6 = 約104万円
- 中央値:(25 + 28) ÷ 2 = 26.5万円
社長の給料500万円という外れ値があると、平均値は104万円と実態とかけ離れた値になってしまいます。一方、中央値は26.5万円で、多くの社員の実態に近い値になります。
中央値が使われる場面
以下のような場合に、中央値が特に有効です。
- 年収や給与の分析
- 不動産価格の統計
- テストの点数分布
- Webサイトの滞在時間
- 年齢の統計
極端な値があるデータでは、中央値の方が実態を正確に表します。
SQLで中央値を求める難しさ
なぜSQLで中央値を求めるのが難しいのでしょうか。
標準的な関数がない
平均値を求めるAVG()関数や、合計を求めるSUM()関数は標準SQLにありますが、中央値を求める標準的な関数は存在しません。
そのため、データベースごとに独自の方法を使う必要があります。
計算方法が複雑
中央値を求めるには、以下のステップが必要です。
- データを並べ替える
- データ数を数える
- 真ん中の位置を特定する
- 真ん中の値を取り出す
- 偶数の場合は2つの値の平均を計算
これをSQLで表現するには、複数の関数や副問い合わせを組み合わせる必要があります。
データベース別の中央値の求め方
主要なデータベースごとに、中央値を求める方法を紹介します。
MySQL・MariaDBでの中央値
MySQLには中央値専用の関数がないため、副問い合わせを使います。
方法1:変数を使った方法
SET @row_index := -1;
SELECT AVG(price) AS median_price
FROM (
SELECT @row_index:=@row_index + 1 AS row_index, price
FROM products
ORDER BY price
) AS subquery
WHERE row_index IN (FLOOR(@row_index / 2), CEIL(@row_index / 2));
方法2:ウィンドウ関数を使った方法(MySQL 8.0以降)
WITH ordered_data AS (
SELECT
price,
ROW_NUMBER() OVER (ORDER BY price) AS row_num,
COUNT(*) OVER () AS total_count
FROM products
)
SELECT AVG(price) AS median_price
FROM ordered_data
WHERE row_num IN (FLOOR((total_count + 1) / 2), CEIL((total_count + 1) / 2));
実例:商品価格の中央値を求める
-- サンプルデータ
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
price INT
);
INSERT INTO products VALUES
(1, '商品A', 1000),
(2, '商品B', 2000),
(3, '商品C', 3000),
(4, '商品D', 4000),
(5, '商品E', 5000);
-- 中央値を求める
WITH ordered_data AS (
SELECT
price,
ROW_NUMBER() OVER (ORDER BY price) AS row_num,
COUNT(*) OVER () AS total_count
FROM products
)
SELECT AVG(price) AS median_price
FROM ordered_data
WHERE row_num IN (FLOOR((total_count + 1) / 2), CEIL((total_count + 1) / 2));
結果:median_price = 3000
PostgreSQLでの中央値
PostgreSQLには、中央値を求める専用の関数があります。
方法1:PERCENTILE_CONT関数(推奨)
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products;
PERCENTILE_CONT(0.5)は、50パーセンタイル(中央値)を求める関数です。0.5は50%を意味します。
方法2:PERCENTILE_DISC関数
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products;
PERCENTILE_DISCは、実際のデータの中から値を選びます。PERCENTILE_CONTは補間(平均)を行います。
違いの例:
データが1, 2, 3, 4の場合:
PERCENTILE_CONT(0.5)→ 2.5(2と3の平均)PERCENTILE_DISC(0.5)→ 2(実際のデータから選択)
グループごとの中央値
SELECT
category,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products
GROUP BY category;
カテゴリごとに中央値を求められます。
SQL Serverでの中央値
SQL Serverでも、PERCENTILE_CONT関数が使えます。
基本的な使い方
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)
OVER () AS median_price
FROM products;
PostgreSQLと少し構文が異なり、OVER ()句が必要です。
グループごとの中央値
SELECT DISTINCT
category,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)
OVER (PARTITION BY category) AS median_price
FROM products;
複数の統計値を同時に取得
SELECT DISTINCT
category,
AVG(price) OVER (PARTITION BY category) AS avg_price,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)
OVER (PARTITION BY category) AS median_price,
MAX(price) OVER (PARTITION BY category) AS max_price
FROM products;
平均値、中央値、最大値を一度に取得できます。
Oracleでの中央値
Oracleには、MEDIAN関数という専用関数があります。
基本的な使い方
SELECT MEDIAN(price) AS median_price
FROM products;
非常にシンプルですね。
グループごとの中央値
SELECT
category,
MEDIAN(price) AS median_price
FROM products
GROUP BY category;
PERCENTILE_CONT関数も使える
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products;
どちらを使っても同じ結果が得られます。
SQLiteでの中央値
SQLiteには中央値専用の関数がないため、工夫が必要です。
方法1:LIMIT句を使った方法
SELECT AVG(price) AS median_price
FROM (
SELECT price
FROM products
ORDER BY price
LIMIT 2 - (SELECT COUNT(*) FROM products) % 2
OFFSET (SELECT (COUNT(*) - 1) / 2 FROM products)
);
方法2:ウィンドウ関数を使った方法
WITH ordered_data AS (
SELECT
price,
ROW_NUMBER() OVER (ORDER BY price) AS row_num,
COUNT(*) OVER () AS total_count
FROM products
)
SELECT AVG(price) AS median_price
FROM ordered_data
WHERE row_num IN (
(total_count + 1) / 2,
(total_count + 2) / 2
);
実務でよく使うパターン
実際の業務でよく使われる中央値の求め方を紹介します。
パターン1:期間を指定して中央値を求める
-- 2024年1月の売上の中央値(PostgreSQL)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median_sales
FROM sales
WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31';
パターン2:カテゴリ別・月別の中央値
-- カテゴリ別・月別の中央値(SQL Server)
SELECT DISTINCT
category,
YEAR(sale_date) AS year,
MONTH(sale_date) AS month,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount)
OVER (PARTITION BY category, YEAR(sale_date), MONTH(sale_date))
AS median_sales
FROM sales
ORDER BY category, year, month;
パターン3:外れ値を除外した中央値
-- 上位5%と下位5%を除外した中央値(PostgreSQL)
WITH filtered_data AS (
SELECT amount
FROM sales
WHERE amount BETWEEN
(SELECT PERCENTILE_CONT(0.05) WITHIN GROUP (ORDER BY amount) FROM sales)
AND
(SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY amount) FROM sales)
)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS trimmed_median
FROM filtered_data;
パターン4:複数列の中央値を一度に求める
-- 売上と利益の中央値を同時に取得(PostgreSQL)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY sales_amount) AS median_sales,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY profit) AS median_profit
FROM sales_data;
パフォーマンスの考慮点
中央値の計算は、データ量が多いと時間がかかります。
インデックスの活用
中央値を求める列にインデックスを作成すると、ソート処理が高速化されます。
CREATE INDEX idx_price ON products(price);
サンプリングの活用
データ量が膨大な場合、全データではなくサンプルから中央値を推定する方法もあります。
-- 10%のサンプルから中央値を推定(PostgreSQL)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS estimated_median
FROM products TABLESAMPLE BERNOULLI(10);
集計テーブルの利用
定期的に中央値を計算する場合、集計テーブルを作成して事前計算しておく方法も有効です。
-- 日次で中央値を集計
CREATE TABLE daily_median AS
SELECT
DATE(sale_date) AS sale_date,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median_amount
FROM sales
GROUP BY DATE(sale_date);
よくある間違いと注意点
NULLの扱い
中央値を求める際、NULL値は自動的に除外されます。
-- NULLを含むデータの中央値
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products;
-- NULL値は計算に含まれない
明示的にNULLを除外したい場合:
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median_price
FROM products
WHERE price IS NOT NULL;
データ型に注意
整数型の列で中央値を求めると、結果も整数になることがあります。
-- 整数型の場合
SELECT MEDIAN(age) FROM users; -- 結果: 30(整数)
-- 小数点まで求めたい場合は型変換
SELECT MEDIAN(CAST(age AS DECIMAL)) FROM users; -- 結果: 30.5
COUNT(*)との違い
中央値は値の大きさの中央であって、件数の中央ではありません。
-- これは間違い(件数の半分を取得)
SELECT price
FROM products
LIMIT (SELECT COUNT(*) / 2 FROM products);
-- 正しい中央値の取得
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)
FROM products;
よくある質問
第1四分位数や第3四分位数も求められる?
はい、PERCENTILE_CONT関数を使えば求められます。
-- 第1四分位数(25%)、中央値(50%)、第3四分位数(75%)
SELECT
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY price) AS Q1,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) AS median,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY price) AS Q3
FROM products;
中央値と最頻値の違いは?
- 中央値(メディアン):データを並べたときの真ん中の値
- 最頻値(モード):最も頻繁に現れる値
最頻値をSQLで求めるには:
SELECT price, COUNT(*) AS frequency
FROM products
GROUP BY price
ORDER BY frequency DESC
LIMIT 1;
重み付き中央値は求められる?
標準的な関数では難しいですが、複雑なSQLで実現できます。
例えば、各データに重みがある場合、重みに応じて行を複製してから中央値を求める方法があります。
実行速度が遅い場合の対処法は?
- 対象列にインデックスを作成
- WHERE句で絞り込んでからデータ数を減らす
- サンプリングを活用
- 定期的な集計が必要ならマテリアライズドビューを使用
まとめ:データベースに合った方法で中央値を求めよう
中央値は、平均値では見えないデータの実態を把握するための重要な指標です。
データベース別の中央値取得方法まとめ
PostgreSQL(推奨):
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column_name)
FROM table_name;
SQL Server:
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column_name)
OVER ()
FROM table_name;
Oracle:
SELECT MEDIAN(column_name)
FROM table_name;
MySQL 8.0以降:
WITH ordered_data AS (
SELECT column_name,
ROW_NUMBER() OVER (ORDER BY column_name) AS row_num,
COUNT(*) OVER () AS total_count
FROM table_name
)
SELECT AVG(column_name)
FROM ordered_data
WHERE row_num IN (FLOOR((total_count + 1) / 2),
CEIL((total_count + 1) / 2));
活用のポイント
- 外れ値がある場合は中央値の方が実態を反映
- グループごとの中央値でカテゴリ別分析が可能
- パフォーマンスにはインデックスが重要
- NULL値は自動的に除外される
使用しているデータベースに合った方法で、効果的にデータ分析を行いましょう。この記事を参考に、ぜひ中央値を活用してみてくださいね!

コメント