MySQLで中央値を計算する簡単な方法


208

MySQLで中央値を計算する最も簡単な(そして遅すぎないことが望ましい)方法は何ですか?私はAVG(x)平均を見つけるために使用しましたが、中央値を計算する簡単な方法を見つけるのに苦労しています。今のところ、すべての行をPHPに返し、並べ替えを行ってから中央の行を選択していますが、単一のMySQLクエリでそれを行う簡単な方法がいくつかあるはずです。

データの例:

id | val
--------
 1    4
 2    7
 3    2
 4    2
 5    9
 6    8
 7    3

並べ替えはvalを与える2 2 3 4 7 8 9ので、中央値はであるの4に対し、SELECT AVG(val)どちらの==である必要があり5ます。


72
MySQLが中央値を計算する関数を持たないという事実に私が頭を悩ませているのは私だけですか?ばかげている。
モニカヘドネック2016年

3
バージョン10.3のものを持っているので、MariaDBは、参照mariadb.com/kb/en/library/median
berturion

回答:


225

MariaDB / MySQLの場合:

SELECT AVG(dd.val) as median_val
FROM (
SELECT d.val, @rownum:=@rownum+1 as `row_number`, @total_rows:=@rownum
  FROM data d, (SELECT @rownum:=0) r
  WHERE d.val is NOT NULL
  -- put some where clause here
  ORDER BY d.val
) as dd
WHERE dd.row_number IN ( FLOOR((@total_rows+1)/2), FLOOR((@total_rows+2)/2) );

Steve Cohenは、最初のパスの後、@ rownumには行の総数が含まれることになると指摘しています。これは中央値を決定するために使用できるため、2番目のパスまたは結合は必要ありません。

またAVG(dd.val)dd.row_number IN(...)レコード数が偶数の場合に中央値を正しく生成するためにも使用されます。推論:

SELECT FLOOR((3+1)/2),FLOOR((3+2)/2); -- when total_rows is 3, avg rows 2 and 2
SELECT FLOOR((4+1)/2),FLOOR((4+2)/2); -- when total_rows is 4, avg rows 2 and 3

最後に、MariaDB 10.3.3+にはMEDIAN関数が含まれています


4
グループの値を表示する方法はありますか?のように:場所/その場所の中央値... select placeのように、テーブルのmedian_value ...とにかく?感謝
saulob 2014年

2
@rowNumは、実行の最後に「合計数」を持ちます。したがって、「すべてカウント」を再度実行する必要がない場合は、これを使用できます(これは私のクエリがそれほど単純ではなかったためです)
Ahmed-Anas

1つのステートメントを持つロジック:(floor((total_rows + 1)/ 2)、floor((total_rows + 2)/ 2))中央値に必要な行を計算するのは素晴らしいです!どのように考えたのかはわかりませんが、素晴らしいです。私が従わない部分は(SELECT @rownum:= 0)rです-これはどのような目的に役立ちますか?
Shanemeister 2017年

最初に変更WHERE 1するWHERE d.val IS NOT NULLので、それを除くNULLネイティブに合わせ、この方法を保つために、行AVG
chiliNUT

1
私の値は2つのテーブルの結合から得られたため、結合後に行の順序が正しいことを確認するために、別のサブクエリを追加する必要がありました。構造は一種でしたselect avg(value) from (select value, row_number from (select a - b as value from a_table join b_table order by value))
Daniel Buckmaster

62

はコメントでオンラインで別の答えを見つけました

ほとんどすべてのSQLの中央値の場合:

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val))) = (COUNT(*)+1)/2

列のインデックスが適切に作成されており、インデックスがフィルタリングと並べ替えに使用されていることを確認してください。計画の説明で確認します。

select count(*) from table --find the number of rows

「中央値」の行番号を計算します。多分使用:median_row = floor(count / 2)

次に、リストから選択します。

select val from table order by val asc limit median_row,1

これにより、必要な値のみを含む1行が返されます。

ジェイコブ


6
@robは編集を手伝ってくれますか?それとも、ベルクロウソリューションに頭を下げるべきですか?(実際には別の解決策に
従う

1
これは「クロス結合」を行うことに注意してください。これは大きなテーブルでは非常に遅いです。
リックジェームズ

1
この答え偶数の行に対しては何返しません
kuttumiah

この答えは、0.1、0.1、0.1、2という値の自明なデータセットなど、一部のデータセットではまったく機能しません。すべての値が異なる場合に機能しますが、値が機能する場合にのみ機能します
Kem Mason

32

MySQLのインストールでは承認されたソリューションが機能せず、空のセットが返されることがわかりましたが、このクエリは、テストしたすべての状況で機能しました。

SELECT x.val from data x, data y
GROUP BY x.val
HAVING SUM(SIGN(1-SIGN(y.val-x.val)))/COUNT(*) > .5
LIMIT 1

1
完全に正しい、インデックス付きのテーブルで完全かつ非常に高速に動作する
Rob

2
これは、ここでのすべての回答の中でmysqlの最速の解決策のようです。テーブルに100万レコードが足りない200
Rob

3
@FrankConijn:1つのテーブルから2回選択します。テーブルの名前がありdata、それは二つの名前で使用されている、xy
ブライアン

3
ただ...私は33K行でテーブルの上にこの正確なクエリと私のmysqldを停止したと言って
Xenonite

1
このクエリは、偶数の行に対して誤った回答を返します。
kuttumiah

26

残念ながら、TheJacobTaylorの回答もvelcrowの回答も、現在のバージョンのMySQLに対して正確な結果を返しません。

上からのベルクロの答えは近いですが、行数が偶数の結果セットでは正しく計算されません。中央値は、1)奇数セットの中央の数値、または2)偶数セットの2つの中央の数値の平均として定義されます。

そこで、奇数と偶数の両方のセットを処理するようにパッチを当てたベルクロのソリューションを次に示します。

SELECT AVG(middle_values) AS 'median' FROM (
  SELECT t1.median_column AS 'middle_values' FROM
    (
      SELECT @row:=@row+1 as `row`, x.median_column
      FROM median_table AS x, (SELECT @row:=0) AS r
      WHERE 1
      -- put some where clause here
      ORDER BY x.median_column
    ) AS t1,
    (
      SELECT COUNT(*) as 'count'
      FROM median_table x
      WHERE 1
      -- put same where clause here
    ) AS t2
    -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
    WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;

これを使用するには、次の3つの簡単な手順に従います。

  1. 上記のコードの「median_table」(2回)をテーブルの名前に置き換えます
  2. "median_column"(3回)を、中央値を検索する列名に置き換えます
  3. WHERE条件がある場合は、「WHERE 1」(2回)を自分のwhere条件に置き換えます。

そして、あなたは文字列値の中央値に対して何をしますか?
リックジェームズ

12

より速い方法を提案します。

行数を取得します。

SELECT CEIL(COUNT(*)/2) FROM data;

次に、ソートされたサブクエリの中央の値を取得します。

SELECT max(val) FROM (SELECT val FROM data ORDER BY val limit @middlevalue) x;

これを乱数の5x10e6データセットでテストしたところ、中央値は10秒未満で見つかります。


3
理由:SELECT val FROM data ORDER BY val limit @middlevalue、1
Bryan

1
最初のコードブロックの変数出力を2番目のコードブロックにどのようにプルしますか?
2012

3
のように、@ middlevalueはどこから来たのですか?
2012

@ブライアン-私はあなたに同意します、それは私にははるかに理にかなっています。そのようにしない理由を見つけたことがありますか?
シェーンN

5
変数は制限句で使用できないため、これは機能しません。
codepk

8

MySQLドキュメントのこのページのコメントには、次の提案があります。

-- (mostly) High Performance scaling MEDIAN function per group
-- Median defined in http://en.wikipedia.org/wiki/Median
--
-- by Peter Hlavac
-- 06.11.2008
--
-- Example Table:

DROP table if exists table_median;
CREATE TABLE table_median (id INTEGER(11),val INTEGER(11));
COMMIT;


INSERT INTO table_median (id, val) VALUES
(1, 7), (1, 4), (1, 5), (1, 1), (1, 8), (1, 3), (1, 6),
(2, 4),
(3, 5), (3, 2),
(4, 5), (4, 12), (4, 1), (4, 7);



-- Calculating the MEDIAN
SELECT @a := 0;
SELECT
id,
AVG(val) AS MEDIAN
FROM (
SELECT
id,
val
FROM (
SELECT
-- Create an index n for every id
@a := (@a + 1) mod o.c AS shifted_n,
IF(@a mod o.c=0, o.c, @a) AS n,
o.id,
o.val,
-- the number of elements for every id
o.c
FROM (
SELECT
t_o.id,
val,
c
FROM
table_median t_o INNER JOIN
(SELECT
id,
COUNT(1) AS c
FROM
table_median
GROUP BY
id
) t2
ON (t2.id = t_o.id)
ORDER BY
t_o.id,val
) o
) a
WHERE
IF(
-- if there is an even number of elements
-- take the lower and the upper median
-- and use AVG(lower,upper)
c MOD 2 = 0,
n = c DIV 2 OR n = (c DIV 2)+1,

-- if its an odd number of elements
-- take the first if its only one element
-- or take the one in the middle
IF(
c = 1,
n = 1,
n = c DIV 2 + 1
)
)
) a
GROUP BY
id;

-- Explanation:
-- The Statement creates a helper table like
--
-- n id val count
-- ----------------
-- 1, 1, 1, 7
-- 2, 1, 3, 7
-- 3, 1, 4, 7
-- 4, 1, 5, 7
-- 5, 1, 6, 7
-- 6, 1, 7, 7
-- 7, 1, 8, 7
--
-- 1, 2, 4, 1

-- 1, 3, 2, 2
-- 2, 3, 5, 2
--
-- 1, 4, 1, 4
-- 2, 4, 5, 4
-- 3, 4, 7, 4
-- 4, 4, 12, 4


-- from there we can select the n-th element on the position: count div 2 + 1 

私見、これは明らかに、複雑なサブセットからの中央値が必要な状況に最適です(私は多数のデータサブセットの個別の中央値を計算する必要がありました)
mblackwell8

私にとってはうまくいきます。5.6.14 MySQL Community Server。11Mのレコード(ディスク上で約20Gb)を持つテーブルには、2つのプライマリインデックス(model_id、price)がありません。表(ろ過後)には、中央値を計算するための500Kレコードがあります。その結果、30Kのレコード(model_id、median_price)ができました。クエリ期間は1.5〜2秒です。スピードは私にとって速いです。
Mikl 14

8

このmysql統計関数をインストールして使用します。http//www.xarg.org/2012/07/statistical-functions-in-mysql/

その後、中央値を計算するのは簡単です:

SELECT median(val) FROM data;

1
私はこれを自分で試してみましたが、その価値はありますが、インストールは超高速/簡単で、グループ化も含めて、宣伝どおりに機能しました。github.com/infusion/udf_infusion
Kem Mason

6

上記のソリューションのほとんどは、テーブルの1つのフィールドに対してのみ機能します。クエリの多くのフィールドの中央値(50パーセンタイル)を取得する必要がある場合があります。

私はこれを使います:

SELECT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(
 GROUP_CONCAT(field_name ORDER BY field_name SEPARATOR ','),
  ',', 50/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) AS `Median`
FROM table_name;

上記の例の「50」を任意のパーセンタイルに置き換えることができ、非常に効率的です。

GROUP_CONCATに十分なメモリがあることを確認してください。次のコマンドで変更できます。

SET group_concat_max_len = 10485760; #10MB max length

詳細:http : //web.performancerasta.com/metrics-tips-calculating-95th-99th-or-any-percentile-with-single-mysql-query/


注意:値が偶数の場合は、2つの中間値のうち高い方の値を取ります。値のオッズ数については、中央値の次に高い値を取ります。
ジョルダーノ2013

6

私は以下のコードをHackerRankで見つけましたが、それは非常にシンプルであり、すべてのケースで機能します。

SELECT M.MEDIAN_COL FROM MEDIAN_TABLE M WHERE  
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL < M.MEDIAN_COL ) = 
  (SELECT COUNT(MEDIAN_COL) FROM MEDIAN_TABLE WHERE MEDIAN_COL > M.MEDIAN_COL );

2
これは、エントリ数が奇数であるテーブルでのみ機能すると思います。エントリが偶数の場合、これには問題がある可能性があります。
Yチャン

4

ベルクロの答えを基にして、別のパラメーターでグループ化されたものの中央値を計算しなければならない人のために:

SELECT grp_field t1 ヴァルFROM SELECT grp_field @ ROWNUM := IF (@ S = grp_field @ ROWNUM + 1 0 AS @ S := IF (@ S = grp_field @ S grp_field AS D ヴァル
   FROM データd  
         row_number
       SELECT @ ROWNUM := 0 @ S := 0 のR
   ORDER BY grp_field D val
 as t1 JOIN SELECT grp_field count (*)as total_rows
   FROM data d
   GROUP BY grp_field
 as t2
 ON t1 grp_field = t2 grp_field
 WHERE t1 行番号     
     = floor total_rows / 2 +1 ;


3

ここにあるユーザー定義関数を使用できます。


3
これは最も便利に見えますが、mysqlを本番サーバーにクラッシュさせる可能性がある不安定なアルファソフトウェアをインストールしたくありません:(
davr

6
そのため、関心のある機能についてソースを調べ、必要に応じて修正または変更し、作成した「独自の」安定した非アルファバージョンをインストールします。これまでに、あまり実績のないコードの提案を微調整するよりもさらに悪い方法です。あなたはそうですか?-)
Alex Martelli

3

奇数のカウントに注意してください-その場合、真ん中にある2つの値の平均を与えます。

SELECT AVG(val) FROM
  ( SELECT x.id, x.val from data x, data y
      GROUP BY x.id, x.val
      HAVING SUM(SIGN(1-SIGN(IF(y.val-x.val=0 AND x.id != y.id, SIGN(x.id-y.id), y.val-x.val)))) IN (ROUND((COUNT(*))/2), ROUND((COUNT(*)+1)/2))
  ) sq

2

テーブルや追加の変数なしで効率的な私のコード:

SELECT
((SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', floor(1+((count(val)-1) / 2))), ',', -1))
+
(SUBSTRING_INDEX(SUBSTRING_INDEX(group_concat(val order by val), ',', ceiling(1+((count(val)-1) / 2))), ',', -1)))/2
as median
FROM table;

3
これはGROUP_CONCAT、このような別の関数内で使用された場合でも1023文字に制限されているため、かなりの量のデータでは失敗します。
Rob Van Dam

2

オプションで、これをストアドプロシージャで行うこともできます。

DROP PROCEDURE IF EXISTS median;
DELIMITER //
CREATE PROCEDURE median (table_name VARCHAR(255), column_name VARCHAR(255), where_clause VARCHAR(255))
BEGIN
  -- Set default parameters
  IF where_clause IS NULL OR where_clause = '' THEN
    SET where_clause = 1;
  END IF;

  -- Prepare statement
  SET @sql = CONCAT(
    "SELECT AVG(middle_values) AS 'median' FROM (
      SELECT t1.", column_name, " AS 'middle_values' FROM
        (
          SELECT @row:=@row+1 as `row`, x.", column_name, "
          FROM ", table_name," AS x, (SELECT @row:=0) AS r
          WHERE ", where_clause, " ORDER BY x.", column_name, "
        ) AS t1,
        (
          SELECT COUNT(*) as 'count'
          FROM ", table_name, " x
          WHERE ", where_clause, "
        ) AS t2
        -- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
        WHERE t1.row >= t2.count/2
          AND t1.row <= ((t2.count/2)+1)) AS t3
    ");

  -- Execute statement
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END//
DELIMITER ;


-- Sample usage:
-- median(table_name, column_name, where_condition);
CALL median('products', 'price', NULL);

これをありがとう!ユーザーは、欠損値(NULL)が値と見なされることに注意する必要があります。この問題を回避するには、 'x IS NOT NULL where condition'を追加します。
giordano 2013

1
@giordanoコードのどの行にx IS NOT NULL追加する必要がありますか?
Przemyslaw Remin

1
@PrzemyslawRemin申し訳ありませんが、私の声明でははっきりしていませんでしたが、SPはすでに欠損値のケースを考慮していることに気付きました。SPは次の方法で呼び出す必要がありますCALL median("table","x","x IS NOT NULL")
Giordano

2

以下に示す私のソリューションは、テーブル、変数、またはサブクエリを作成することなく、1つのクエリでのみ機能します。さらに、group-byクエリで各グループの中央値を取得できます(これが私が必要としたものです!):

SELECT `columnA`, 
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(`columnB` ORDER BY `columnB`), ',', CEILING((COUNT(`columnB`)/2))), ',', -1) medianOfColumnB
FROM `tableC`
-- some where clause if you want
GROUP BY `columnA`;

これは、group_concatおよびsubstring_indexのスマートな使用により機能します。

ただし、大きなgroup_concatを許可するには、group_concat_max_lenをより高い値(デフォルトでは1024文字)に設定する必要があります。あなたはそれをそのように設定できます(現在のSQLセッション用):

SET SESSION group_concat_max_len = 10000; 
-- up to 4294967295 in 32-bits platform.

group_concat_max_lenの詳細情報:https ://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_group_concat_max_len


2

ベルクロウの答えの別のリフですが、単一の中間テーブルを使用し、追加のクエリを実行して計算するのではなく、行番号付けに使用される変数を利用してカウントを取得します。また、最初の行が行0になるようにカウントを開始し、FloorとCeilを使用して行の中央値を選択できるようにします。

SELECT Avg(tmp.val) as median_val
    FROM (SELECT inTab.val, @rows := @rows + 1 as rowNum
              FROM data as inTab,  (SELECT @rows := -1) as init
              -- Replace with better where clause or delete
              WHERE 2 > 1
              ORDER BY inTab.val) as tmp
    WHERE tmp.rowNum in (Floor(@rows / 2), Ceil(@rows / 2));

2
SELECT 
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            GROUP_CONCAT(field ORDER BY field),
            ',',
            ((
                ROUND(
                    LENGTH(GROUP_CONCAT(field)) - 
                    LENGTH(
                        REPLACE(
                            GROUP_CONCAT(field),
                            ',',
                            ''
                        )
                    )
                ) / 2) + 1
            )),
            ',',
            -1
        )
FROM
    table

上記は私にはうまくいくようです。


偶数の値に対して正しい中央値を返していません。たとえば、中央値は{98,102,102,98}isです100が、コードからは得られ102ます。奇数の場合は問題なく動作しました。
Nomiluks

1

私は2つのクエリアプローチを使用しました。

  • 最初にカウント、最小、最大、平均を取得する
  • 中央値を取得するための "LIMIT @ count / 2、1"および "ORDER BY .."句を含む2番目のステートメント(準備済みステートメント)

これらは関数defnでラップされているため、1回の呼び出しですべての値を返すことができます。

範囲が静的であり、データが頻繁に変更されない場合は、これらの値を事前に計算/格納し、毎回最初からクエリするのではなく、格納された値を使用する方が効率的です。


1

中央値とパーセンタイルのソリューションが必要だったので、このスレッドの結果に基づいて、シンプルで非常に柔軟な関数を作成しました。私のプロジェクトに簡単に組み込むことができる「既製」の機能を見つけた場合は、自分でも幸せだと思うので、すぐに共有することにしました。

function mysql_percentile($table, $column, $where, $percentile = 0.5) {

    $sql = "
            SELECT `t1`.`".$column."` as `percentile` FROM (
            SELECT @rownum:=@rownum+1 as `row_number`, `d`.`".$column."`
              FROM `".$table."` `d`,  (SELECT @rownum:=0) `r`
              ".$where."
              ORDER BY `d`.`".$column."`
            ) as `t1`, 
            (
              SELECT count(*) as `total_rows`
              FROM `".$table."` `d`
              ".$where."
            ) as `t2`
            WHERE 1
            AND `t1`.`row_number`=floor(`total_rows` * ".$percentile.")+1;
        ";

    $result = sql($sql, 1);

    if (!empty($result)) {
        return $result['percentile'];       
    } else {
        return 0;
    }

}

使用方法は非常に簡単で、現在のプロジェクトの例です。

...
$table = DBPRE."zip_".$slug;
$column = 'seconds';
$where = "WHERE `reached` = '1' AND `time` >= '".$start_time."'";

    $reaching['median'] = mysql_percentile($table, $column, $where, 0.5);
    $reaching['percentile25'] = mysql_percentile($table, $column, $where, 0.25);
    $reaching['percentile75'] = mysql_percentile($table, $column, $where, 0.75);
...

1

これが私のやり方です。もちろん、それをプロシージャに入れることもできます:-)

SET @median_counter = (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`);

SET @median = CONCAT('SELECT `val` FROM `data` ORDER BY `val` LIMIT ', @median_counter, ', 1');

PREPARE median FROM @median;

EXECUTE median;

@median_counterもしあなたがそれを実質ならば、あなたは変数を避けることができます:

SET @median = CONCAT( 'SELECT `val` FROM `data` ORDER BY `val` LIMIT ',
                      (SELECT FLOOR(COUNT(*)/2) - 1 AS `median_counter` FROM `data`),
                      ', 1'
                    );

PREPARE median FROM @median;

EXECUTE median;

1

この方法には、サブクエリなしの偶数と奇数の両方が含まれているようです。

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0

t2テーブルとは何ですか?
xliiv

1

@bobの回答に基づいて、これはクエリを一般化し、いくつかの基準でグループ化された複数の中央値を返す機能を備えています。

たとえば、自動車の中古車の販売価格の中央値を年月でグループ化するとします。

SELECT 
    period, 
    AVG(middle_values) AS 'median' 
FROM (
    SELECT t1.sale_price AS 'middle_values', t1.row_num, t1.period, t2.count
    FROM (
        SELECT 
            @last_period:=@period AS 'last_period',
            @period:=DATE_FORMAT(sale_date, '%Y-%m') AS 'period',
            IF (@period<>@last_period, @row:=1, @row:=@row+1) as `row_num`, 
            x.sale_price
          FROM listings AS x, (SELECT @row:=0) AS r
          WHERE 1
            -- where criteria goes here
          ORDER BY DATE_FORMAT(sale_date, '%Y%m'), x.sale_price
        ) AS t1
    LEFT JOIN (  
          SELECT COUNT(*) as 'count', DATE_FORMAT(sale_date, '%Y-%m') AS 'period'
          FROM listings x
          WHERE 1
            -- same where criteria goes here
          GROUP BY DATE_FORMAT(sale_date, '%Y%m')
        ) AS t2
        ON t1.period = t2.period
    ) AS t3
WHERE 
    row_num >= (count/2) 
    AND row_num <= ((count/2) + 1)
GROUP BY t3.period
ORDER BY t3.period;

1

多くの場合、テーブル全体だけでなく、IDに関する集計についても中央値を計算する必要があります。つまり、テーブル内の各IDの中央値を計算します。各IDには多数のレコードがあります。(優れたパフォーマンスと多くのSQLで動作し、偶数と奇数の問題を修正します。さまざまなMedian-methodsのパフォーマンスについての詳細https://sqlperformance.com/2012/08/t-sql-queries/median

SELECT our_id, AVG(1.0 * our_val) as Median
FROM
( SELECT our_id, our_val, 
  COUNT(*) OVER (PARTITION BY our_id) AS cnt,
  ROW_NUMBER() OVER (PARTITION BY our_id ORDER BY our_val) AS rn
  FROM our_table
) AS x
WHERE rn IN ((cnt + 1)/2, (cnt + 2)/2) GROUP BY our_id;

それが役に立てば幸い


それが最良の解決策です。ただし、大規模なデータセットの場合は、各セットのすべてのアイテムについて再カウントされるため、速度が低下します。より高速にするには、サブクエリを分離するために "COUNT(*)"を入れてください。
Slava Murygin、2018

1

MySQLはバージョン8.0以降のウィンドウ関数をサポートしており、以下を使用できます(ROW_NUMBERまたは、スポーツランキングのように、同じ値に同じランクを割り当てるため、使用しないでください)。DENSE_RANKRANK

SELECT AVG(t1.val) AS median_val
  FROM (SELECT val, 
               ROW_NUMBER() OVER(ORDER BY val) AS rownum
          FROM data) t1,
       (SELECT COUNT(*) AS num_records FROM data) t2
 WHERE t1.row_num IN
       (FLOOR((t2.num_records + 1) / 2), 
        FLOOR((t2.num_records + 2) / 2));

0

MySQLにROW_NUMBERがある場合、MEDIANは(このSQL Serverクエリに触発されたものです):

WITH Numbered AS 
(
SELECT *, COUNT(*) OVER () AS Cnt,
    ROW_NUMBER() OVER (ORDER BY val) AS RowNum
FROM yourtable
)
SELECT id, val
FROM Numbered
WHERE RowNum IN ((Cnt+1)/2, (Cnt+2)/2)
;

INは、エントリが偶数の場合に使用されます。

グループごとの中央値を検索する場合は、OVER句でPARTITION BYグループのみを使用します。

ロブ


1
いいえ、いいえROW_NUMBER OVER、PARTITION BYはありません。これはMySqlであり、PostgreSQL、IBM DB2、MS SQL Serverなどの実際のDBエンジンではありません;-)。
Alex Martelli、

0

以前のすべてを読んだ後、それらは実際の要件と一致しなかったので、手順や複雑なステートメントを必要としない独自のものを実装しました。 GROUP_CONCATた。MEDIANを取得したい列のすべての値とCOUNT DIV BYを適用しました2次のクエリのように、リストの中央から値を抽出します。

(POSは、中央値を取得する列の名前です)

(query) SELECT
SUBSTRING_INDEX ( 
   SUBSTRING_INDEX ( 
       GROUP_CONCAT(pos ORDER BY CAST(pos AS SIGNED INTEGER) desc SEPARATOR ';') 
    , ';', COUNT(*)/2 ) 
, ';', -1 ) AS `pos_med`
FROM table_name
GROUP BY any_criterial

これが他の多くのコメントがこのWebサイトから私に対してあったように、誰かに役立つことを願っています。


0

このクエリを使用できる正確な行数がわかっている場合:

SELECT <value> AS VAL FROM <table> ORDER BY VAL LIMIT 1 OFFSET <half>

どこ <half> = ceiling(<size> / 2.0) - 1


0

セットの中央年齢を決定するために必要な約10億行を含むデータベースがあります。10億行を並べ替えるのは難しいですが、見つかった個別の値(年齢の範囲は0〜100)を集計すると、このリストを並べ替え、いくつかの算術マジックを使用して、次のように必要なパーセンタイルを見つけることができます。

with rawData(count_value) as
(
    select p.YEAR_OF_BIRTH
        from dbo.PERSON p
),
overallStats (avg_value, stdev_value, min_value, max_value, total) as
(
  select avg(1.0 * count_value) as avg_value,
    stdev(count_value) as stdev_value,
    min(count_value) as min_value,
    max(count_value) as max_value,
    count(*) as total
  from rawData
),
aggData (count_value, total, accumulated) as
(
  select count_value, 
    count(*) as total, 
        SUM(count(*)) OVER (ORDER BY count_value ROWS UNBOUNDED PRECEDING) as accumulated
  FROM rawData
  group by count_value
)
select o.total as count_value,
  o.min_value,
    o.max_value,
    o.avg_value,
    o.stdev_value,
    MIN(case when d.accumulated >= .50 * o.total then count_value else o.max_value end) as median_value,
    MIN(case when d.accumulated >= .10 * o.total then count_value else o.max_value end) as p10_value,
    MIN(case when d.accumulated >= .25 * o.total then count_value else o.max_value end) as p25_value,
    MIN(case when d.accumulated >= .75 * o.total then count_value else o.max_value end) as p75_value,
    MIN(case when d.accumulated >= .90 * o.total then count_value else o.max_value end) as p90_value
from aggData d
cross apply overallStats o
GROUP BY o.total, o.min_value, o.max_value, o.avg_value, o.stdev_value
;

このクエリは、dbをサポートするウィンドウ関数(ROWS UNBOUNDED PRECEDINGを含む)に依存しますが、それがわからない場合は、aggData CTEをそれ自体と結合し、以前のすべての合計を「累積」列に集計して、値には、指定されたパーセンタイルが含まれます。上記のサンプルは、p10、p25、p50(中央値)、p75、およびp90を計算します。

-クリス


0

引用元:http : //mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

私は別の方法を提案します、joinなしで文字列を操作ます

大きなデータを含むテーブルではチェックしませんでしたが、小さな/中程度のテーブルでは問題なく動作します。

ここで良いことは、それがGROUPINGによっても機能することです。それがため、複数の項目の中央値を返すことができることです。

テストテーブルのテストコードは次のとおりです。

DROP TABLE test.test_median
CREATE TABLE test.test_median AS
SELECT 'book' AS grp, 4 AS val UNION ALL
SELECT 'book', 7 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 2 UNION ALL
SELECT 'book', 9 UNION ALL
SELECT 'book', 8 UNION ALL
SELECT 'book', 3 UNION ALL

SELECT 'note', 11 UNION ALL

SELECT 'bike', 22 UNION ALL
SELECT 'bike', 26 

そして、各グループの中央値を見つけるためのコード:

SELECT grp,
         SUBSTRING_INDEX( SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val), ',', COUNT(*)/2 ), ',', -1) as the_median,
         GROUP_CONCAT(val ORDER BY val) as all_vals_for_debug
FROM test.test_median
GROUP BY grp

出力:

grp | the_median| all_vals_for_debug
bike| 22        | 22,26
book| 4         | 2,2,3,4,7,8,9
note| 11        | 11

「{22,26}」の中央値は24にすべきだと思いませんか?
Nomiluks 2017

0

場合によっては、中央値は次のように計算されます。

「中央値」は、数値順に並べられた数値リストの「中央」の値です。偶数カウントセットの場合、中央値は2つの中央値の平均です。そのための簡単なコードを作成しました。

$midValue = 0;
$rowCount = "SELECT count(*) as count {$from} {$where}";

$even = FALSE;
$offset = 1;
$medianRow = floor($rowCount / 2);
if ($rowCount % 2 == 0 && !empty($medianRow)) {
  $even = TRUE;
  $offset++;
  $medianRow--;
}

$medianValue = "SELECT column as median 
               {$fromClause} {$whereClause} 
               ORDER BY median 
               LIMIT {$medianRow},{$offset}";

$medianValDAO = db_query($medianValue);
while ($medianValDAO->fetch()) {
  if ($even) {
    $midValue = $midValue + $medianValDAO->median;
  }
  else {
    $median = $medianValDAO->median;
  }
}
if ($even) {
  $median = $midValue / 2;
}
return $median;

返される$ medianは必要な結果になります:-)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.