用MySQL计算中位数的简单方法


207

用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


71
我是MySQL唯一没有计算中位数的函数的人吗?荒谬。
莫妮卡·赫德内克

3
自10.3版起的MariaDB有一个,请参阅mariadb.com/kb/zh-CN/library/median
berturion

Answers:


224

在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将包含行的总数。这可用于确定中位数,因此不需要第二遍或连接。

另外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
有什么办法可以显示组值?例如:地点/该地点的中位数...类似选择地点,表格中的中位数值... 谢谢
saulob

2
@rowNum将在执行结束时具有“总数”。因此,如果您想避免再次执行“全部计算”(这是我的情况,因为我的查询并非如此简单),则可以使用它
Ahmed-Anas

有一个语句的逻辑:(floor((total_rows + 1)/ 2),floor((total_rows + 2)/ 2))计算中位数所需的行很棒!不确定您如何看待,但这真是太好了。我不关注的部分是(SELECT @rownum:= 0)r-这有什么作用?
Shanemeister

将第一个更改为WHERE 1WHERE d.val IS NOT NULL以便排除NULL行,以使此方法与本机保持一致AVG
chiliNUT

1
我的值来自两张表的联接,因此我必须添加另一个子查询,以确保联接后行顺序正确!结构有点像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

这应该只返回您想要的值的一行。

雅各布


6
@rob您能帮忙编辑吗?还是我应该屈服于velcrow解决方案?(实际上不确定是否要
遵循

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上最快的解决方案,200ms短于表中的一百万条记录
Rob

3
@FrankConijn:它从一张表中选择两次。该表的名称是data,并且正与两个名称x和一起使用y
布莱恩

3
只是说我在具有33k行的表上用这个确切的查询使我的mysqld停顿了...
Xenonite

1
对于偶数行,此查询返回错误答案
kuttumiah

26

不幸的是,TheJacobTaylor的答案和velcrow的答案都无法返回当前版本MySQL的准确结果。

魔术贴从上面的答案很接近,但是对于行数偶数的结果集,它不能正确计算。中位数定义为1)奇数集上的中间数,或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. 将“ median_column”(出现3次)替换为您要查找其中位数的列名
  3. 如果您有WHERE条件,请用where条件替换“ WHERE 1”(2次)

而且,您如何处理字符串值的中位数?
瑞克·詹姆斯

12

我提出了一种更快的方法。

获取行数:

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

然后将中间值带入已排序的子查询中:

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

我使用5x10e6随机数数据集对此进行了测试,它将在10秒内找到中值。


3
为什么不这样做:从数据中选择val ORDER BY val limit @ middlevalue,1
Bryan

1
如何将第一个代码块的变量输出拉入第二个代码块?
2012年

3
就像@middlevalue来自哪里?
2012年

@Bryan-我同意你的观点,这对我来说意义更大。您是否找到了不这样做的理由?
Shane N

5
这不能工作,因为不能在limit子句中使用变量。
–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 2012年

对我来说很好。5.6.14 MySQL社区服务器。具有11M条记录(在磁盘上大约20Gb)的表具有两个非主要索引(model_id,price)。在表格中(过滤后),我们有500K条记录来计算中位数。结果,我们有3万条记录(model_id,median_price)。查询时间为1.5-2秒。速度对我来说是最快的。
Mikl 2014年

7

安装并使用此mysql统计功能:http : //www.xarg.org/2012/07/statistical-functions-in-mysql/

之后,计算中位数很容易:

SELECT median(val) FROM data;

1
我只是亲自尝试了一下,对于它的价值,安装它非常快速/简便,并且按广告宣传进行工作,包括分组,例如“选择名称,按名称从t1组中取中值(x)” – github源代码在这里:github.com/infusion/udf_infusion
Kem Mason

6

上面的大多数解决方案仅适用于表的一个字段,您可能需要获取查询中许多字段的中位数(第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/


请注意:对于偶数个值,它采用两个中间值中的较高者。对于奇数值,它取中位数后的下一个较高的值。
giordano

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. Chang

4

建立在维可牢尼龙搭扣的答案基础上,对于那些不得不对由另一个参数分组的东西进行中值处理的人:

选择grp_field t1 VAL FROM SELECT grp_field @ ROWNUM := IF (@ 小号= grp_field @ ROWNUM + 1 0 AS @ 小号:= IF (@ 小号= grp_field @ 小号grp_field AS d VAL
   FROM 数据d  
         row_number
       SELECT @ rownum := 0 @ s := 0 r
   ORDER BY grp_field d VAL
 T1 JOIN SELECT grp_field 计数(*)作为TOTAL_ROWS
   FROM 数据D
   GROUP BY grp_field
 t2的
 ON T1 grp_field = t2 grp_field
 WHERE T1 row_number     
     = 楼板total_rows / 2 +1 ;


3

您可以使用在此处找到的用户定义函数。


3
这看起来是最有用的,但是我不想安装不稳定的alpha软件,它可能导致mysql崩溃到我的生产服务器上:(
davr

6
因此,请研究其源代码以获取感兴趣的功能,对其进行修复或根据需要对其进行修改,并在完成后安装“您自己的”稳定且非Alpha版本-与对未经证实的代码建议进行同样的调整相比,这有何缺点?你得到SO - ?)
亚历克斯·马尔泰利

3

注意奇数计数-在这种情况下,给出中间两个值的平均值。

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。
giordano

1
@giordano x IS NOT NULL应该在代码的哪一行添加?
Przemyslaw Remin's

1
@PrzemyslawRemin抱歉,我在声明中不清楚,现在我意识到SP已经考虑了缺少值的情况。该SP应该被称为以这样的方式CALL median("table","x","x IS NOT NULL")
佐丹奴

2

下面介绍的我的解决方案仅在一种查询中起作用,而无需创建表,变量甚至子查询。另外,它允许您在按组查询中获得每个组的中位数(这就是我所需要的!):

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

Velcrow答案的另一种形式,但使用了一个中间表,并利用了用于行编号的变量来获取计数,而不是执行额外的查询来计算它。同时开始计数,以便第一行是第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}100而您的代码给出102。对于奇数它工作正常。
Nomiluks

1

我使用了两种查询方法:

  • 第一个获得计数,最小,最大和平均
  • 第二个语句(准备好的语句),带有“ LIMIT @ count / 2,1”和“ ORDER BY ..”子句,以获取中位数

这些都包装在函数defn中,因此可以从一个调用中返回所有值。

如果范围是静态的,并且数据不经常更改,则预先计算/存储这些值并使用存储的值可能比每次从头开始查询更有效。


1

因为我只需要一个中位数AND百分位数解决方案,所以我根据该线程中的发现创建了一个简单且相当灵活的函数。我知道,如果我发现容易在项目中包含的“现成”功能,我会感到很高兴,因此我决定快速分享一下:

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

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方法的性能的问题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

1

MySQL自8.0版开始支持窗口功能,您可以使用ROW_NUMBERDENSE_RANK((请勿使用,RANK因为它会将相同的排名分配给相同的值,例如体育排名):

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,不分区,这都不是;这是MySql,而不是像PostgreSQL,IBM DB2,MS SQL Server等真正的数据库引擎;-)。
Alex Martelli

0

在阅读了所有以前的内容之后,它们与我的实际要求不符,因此我实现了自己的实例,不需要任何过程或复杂的语句,只是GROUP_CONCAT我想获取中位数并应用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

我希望这对某人有用,就像该网站上的许多其他评论对我有用。


0

知道确切的行数,您可以使用以下查询:

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

哪里 <half> = ceiling(<size> / 2.0) - 1


0

我有一个数据库,其中包含大约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
;

此查询取决于您的数据库支持的窗口函数(包括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

我建议另一种方法,不加入,但与字符串

我没有用大数据表检查过它,但是中小型表却很好。

这里的好处是,它也可以通过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

0

在某些情况下,中位数的计算方式如下:

当按值对数字进行排序时,“中位数”是数字列表中的“中间”值。对于偶数集,中位数是两个中间值的平均值。我为此创建了一个简单的代码:

$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为必填结果:-)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.