web-dev-qa-db-ja.com

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

MySQLで中央値を計算する最も簡単な(そしておそらく遅すぎない)方法は何ですか?平均を見つけるためにAVG(x)を使用しましたが、中央値を計算する簡単な方法を見つけるのに苦労しています。今のところ、すべての行をPHPに返し、並べ替えを行ってから中央の行を選択していますが、1つの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) which == 5です。

184
davr

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関数が含まれています

205
velcrow

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

ほとんどすべての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

列のインデックスが適切に作成されており、そのインデックスがフィルタリングとソートに使用されていることを確認してください。 EXPLAIN PLANで検証します。

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行が返されます。

ジェイコブ

56
TheJacobTaylor

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
29
zookatron

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

上記のVelcroの答えは近いですが、偶数行の結果セットについては正しく計算されません。中央値は、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条件に置き換えます
18
bob

もっと速い方法を提案します。

行数を取得します。

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

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

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

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

9
Reggie Edwards

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 

上記のソリューションのほとんどは、テーブルの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/

5
Nico

別のパラメーターでグループ化されたものの中央値をオフにする必要がある人のために、ベルクロの答えから構築します。

 SELECT grp_field、t1.val FROM(
 SELECT grp_field、@rownum:= IF(@s = grp_field、@rownum + 1、0)AS row_number、
 @s: = IF(@s = grp_field、@s、grp_field)AS sec、d.val 
 FROM data d、(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.row_number = floor(total_rows/2)+1; 
4
Doug

私は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 );
4

奇数値のカウントに注意します-その場合、中央の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
3
Franz K.

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

3
Alex Martelli

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

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
Steve Cohen

以下に示す私のソリューションは、テーブル、変数、またはサブクエリを作成することなく、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
didier2l
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

上記は私のために働くようです。

2
Nochum Sossonko

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

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);
2
bob

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

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

T1から中央値(x)を選択

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;
2
Oscar Canon

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

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

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

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
bezoo

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

  • count、min、max、avgを取得する最初のもの
  • 中央値を取得するための「LIMIT @ count/2、1」および「ORDER BY ..」句を含む2番目の文(準備済みステートメント)

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

範囲が静的でデータが頻繁に変更されない場合、毎回ゼロからクエリを実行する代わりに、これらの値を事前計算/保存し、保存された値を使用する方が効率的です。

1
btk

多くの場合、テーブル全体だけでなく、IDに関する集計の中央値を計算する必要があります。つまり、各IDに多くのレコードがあるテーブルの各IDの中央値を計算します。 (良好なパフォーマンスと多くのSQLで動作し、偶数とオッズの問題を修正し、異なるMedianメソッドのパフォーマンスについて詳しく説明します 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;

それが役に立てば幸い

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
pucawo

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グループのみを検索します。

ロブ

0
Rob Farley

セット内の年齢の中央値を決定するために必要な約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
Chris Knoll
create table med(id integer);
insert into med(id) values(1);
insert into med(id) values(2);
insert into med(id) values(3);
insert into med(id) values(4);
insert into med(id) values(5);
insert into med(id) values(6);

select (MIN(count)+MAX(count))/2 from 
(select case when (select count(*) from 
med A where A.id<B.id)=(select count(*)/2 from med) OR 
(select count(*) from med A where A.id>B.id)=(select count(*)/2 
from med) then cast(B.id as float)end as count from med B) C;

 ?column? 
----------
  3.5
(1 row)

OR

select cast(avg(id) as float) from 
(select t1.id from med t1 JOIN med t2 on t1.id!= t2.id 
group by t1.id having ABS(SUM(SIGN(t1.id-t2.id)))=1) A;
0
Dwipam Katariya

次のSQLコードは、ユーザー定義変数を使用してMySQLの中央値を計算するのに役立ちます。

create table employees(salary int);

insert into employees values(8);
insert into employees values(23);
insert into employees values(45);
insert into employees values(123);
insert into employees values(93);
insert into employees values(2342);
insert into employees values(2238);

select * from employees;

Select salary from employees  order by salary;

set @rowid=0;
set @cnt=(select count(*) from employees);
set @middle_no=ceil(@cnt/2);
set @odd_even=null;

select AVG(salary) from 
(select salary,@rowid:=@rowid+1 as rid, (CASE WHEN(mod(@cnt,2)=0) THEN @odd_even:=1 ELSE @odd_even:=0 END) as odd_even_status  from employees  order by salary) as tbl where tbl.rid=@middle_no or tbl.rid=(@middle_no+@odd_even);

詳細な説明が必要な場合は、こちらを参照してください blog。

0

私はこの答えが非常に役立つと思いました- https://www.eversql.com/how-to-calculate-median-value-in-mysql-using-a-simple-sql-query/

SET @rowindex := -1;

SELECT
   AVG(g.grade)
FROM
   (SELECT @rowindex:=@rowindex + 1 AS rowindex,
       grades.grade AS grade
    FROM grades
    ORDER BY grades.grade) AS g
WHERE
g.rowindex IN (FLOOR(@rowindex / 2) , CEIL(@rowindex / 2));
0
Kwex

これらのメソッドは、同じテーブルから2回選択します。ソースデータが高価なクエリから来ている場合、これはそれを二度実行することを避ける方法です:

select KEY_FIELD, AVG(VALUE_FIELD) MEDIAN_VALUE
from (
    select KEY_FIELD, VALUE_FIELD, RANKF
    , @rownumr := IF(@prevrowidr=KEY_FIELD,@rownumr+1,1) RANKR
    , @prevrowidr := KEY_FIELD
    FROM (
        SELECT KEY_FIELD, VALUE_FIELD, RANKF
        FROM (
            SELECT KEY_FIELD, VALUE_FIELD 
            , @rownumf := IF(@prevrowidf=KEY_FIELD,@rownumf+1,1) RANKF
            , @prevrowidf := KEY_FIELD     
            FROM (
                SELECT KEY_FIELD, VALUE_FIELD 
                FROM (
                    -- some expensive query
                )   B
                ORDER BY  KEY_FIELD, VALUE_FIELD
            ) C
            , (SELECT @rownumf := 1) t_rownum
            , (SELECT @prevrowidf := '*') t_previd
        ) D
        ORDER BY  KEY_FIELD, RANKF DESC
    ) E
    , (SELECT @rownumr := 1) t_rownum
    , (SELECT @prevrowidr := '*') t_previd
) F
WHERE RANKF-RANKR BETWEEN -1 and 1
GROUP BY KEY_FIELD
0
RobbertNix

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

「中央値」とは、値の順序で並べられた場合の数値リストの「中間」値です。偶数カウントセットの場合、中央値は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は、必要な結果になります:-)

0
jitendrapurohit

から取得: http://mdb-blog.blogspot.com/2015/06/mysql-find-median-nth-element-without.html

別の方法、joinなしをお勧めしますが、stringsを使用します

私は大きなデータを持つテーブルでそれをチェックしませんでしたが、小さな/中規模のテーブルはうまく動作します。

ここで良いことは、それがby 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
0
mr.baby123

@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;
0
Ariel Allon

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

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

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

0
ZhekaKozlov

ディメンション別にグループ化された中央値:

SELECT your_dimension, avg(t1.val) as median_val FROM (
SELECT @rownum:=@rownum+1 AS `row_number`,
   IF(@dim <> d.your_dimension, @rownum := 0, NULL),
   @dim := d.your_dimension AS your_dimension,
   d.val
   FROM data d,  (SELECT @rownum:=0) r, (SELECT @dim := 'something_unreal') d
  WHERE 1
  -- put some where clause here
  ORDER BY d.your_dimension, d.val
) as t1
INNER JOIN  
(
  SELECT d.your_dimension,
    count(*) as total_rows
  FROM data d
  WHERE 1
  -- put same where clause here
  GROUP BY d.your_dimension
) as t2 USING(your_dimension)
WHERE 1
AND t1.row_number in ( floor((total_rows+1)/2), floor((total_rows+2)/2) )

GROUP BY your_dimension;
0
Vladimir_M