SQLで中央値を求める方法完全ガイド!データベース別の書き方を徹底解説

「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にありますが、中央値を求める標準的な関数は存在しません

そのため、データベースごとに独自の方法を使う必要があります。

計算方法が複雑

中央値を求めるには、以下のステップが必要です。

  1. データを並べ替える
  2. データ数を数える
  3. 真ん中の位置を特定する
  4. 真ん中の値を取り出す
  5. 偶数の場合は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で実現できます。

例えば、各データに重みがある場合、重みに応じて行を複製してから中央値を求める方法があります。

実行速度が遅い場合の対処法は?

  1. 対象列にインデックスを作成
  2. WHERE句で絞り込んでからデータ数を減らす
  3. サンプリングを活用
  4. 定期的な集計が必要ならマテリアライズドビューを使用

まとめ:データベースに合った方法で中央値を求めよう

中央値は、平均値では見えないデータの実態を把握するための重要な指標です。

データベース別の中央値取得方法まとめ

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値は自動的に除外される

使用しているデータベースに合った方法で、効果的にデータ分析を行いましょう。この記事を参考に、ぜひ中央値を活用してみてくださいね!

コメント

タイトルとURLをコピーしました