236

用 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

4

47 回答 47

252

在 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 函数

于 2011-08-31T21:53:36.790 回答
70

我刚刚在评论中在线找到了另一个答案

对于几乎所有 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

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

雅各布

于 2009-08-18T01:02:51.077 回答
35

我发现接受的解决方案在我的 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
于 2012-06-04T06:59:33.310 回答
27

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

Velcro 从上面的答案很接近,但它不能正确计算具有偶数行的结果集。中位数被定义为 1) 奇数集的中间数,或 2) 偶数集的两个中间数的平均值。

因此,这是修补了处理奇数和偶数集的 velcro 解决方案:

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 条件
于 2013-05-21T22:03:02.433 回答
13

我提出一个更快的方法。

获取行数:

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

然后在排序的子查询中取中间值:

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

我用一个 5x10e6 的随机数数据集对此进行了测试,它会在 10 秒内找到中位数。

于 2011-05-31T00:05:20.840 回答
11

安装和使用这个mysql统计函数:http ://www.xarg.org/2012/07/statistical-functions-in-mysql/

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

SELECT median(val) FROM data;
于 2014-09-12T14:18:03.833 回答
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 
于 2009-08-18T00:20:48.510 回答
7

我在 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 );
于 2017-06-13T13:17:26.193 回答
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/

于 2013-08-13T11:33:31.003 回答
4

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

于 2009-08-18T00:19:52.860 回答
4

如果 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 group 即可。

于 2009-08-18T00:51:20.207 回答
4

根据魔术贴的答案,对于那些必须对按另一个参数分组的东西做中间值的人:

选择 grp_field, t1.val FROM (
   选择 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 数据 d, (SELECT @rownum:=0, @s:=0) r
  按 grp_field、d.val 排序
) 作为 t1 加入 (
  SELECT grp_field, count(*) as total_rows
  FROM 数据
  按 grp_field 分组
) 作为 t2
ON t1.grp_field = t2.grp_field
WHERE t1.row_number=floor(total_rows/2)+1;

于 2012-03-17T23:49:46.603 回答
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
于 2010-11-17T18:21:29.980 回答
3

我的代码,无需表格或其他变量即可高效:

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;
于 2013-04-23T01:07:14.477 回答
3

归档完美中位数的单个查询:

SELECT 
COUNT(*) as total_rows, 
IF(count(*)%2 = 1, CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*)), ',', -1) AS DECIMAL), ROUND((CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) + CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(val ORDER BY val SEPARATOR ','), ',', 50/100 * COUNT(*)), ',', -1) AS DECIMAL)) / 2)) as median, 
AVG(val) as average 
FROM 
data
于 2020-05-27T10:36:49.247 回答
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);
于 2013-05-21T23:24:05.003 回答
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

于 2014-04-18T08:45:52.127 回答
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));
于 2014-08-12T18:30:11.680 回答
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

以上似乎对我有用。

于 2016-11-22T22:02:27.450 回答
1

我使用了两种查询方法:

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

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

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

于 2010-07-14T18:26:50.333 回答
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);
...
于 2013-06-21T09:19:53.503 回答
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;
于 2014-03-25T17:06:09.987 回答
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;
于 2016-11-23T01:31:45.577 回答
1

通常,我们可能不仅需要计算整个表的中位数,还需要计算与我们的 ID 相关的聚合。换句话说,计算我们表中每个 ID 的中位数,其中每个 ID 都有许多记录。(良好的性能和适用于许多 SQL + 修复偶数和赔率问题,更多关于不同中位数方法的性能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;

希望能帮助到你

于 2017-04-21T22:06:41.553 回答
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));
于 2020-04-30T03:13:33.807 回答
1

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

set @ct := (select count(1) from station);
set @row := 0;

select avg(a.val) as median from 
(select * from  table order by val) a
where (select @row := @row + 1)
between @ct/2.0 and @ct/2.0 +1;
于 2020-10-22T19:11:32.750 回答
1

ORACLE 的简单解决方案:

SELECT ROUND(MEDIAN(Lat_N), 4) FROM Station;

易于理解的 MySQL 解决方案:

select case MOD(count(lat_n),2) 
when 1 then (select round(S.LAT_N,4) from station S where (select count(Lat_N) from station where Lat_N < S.LAT_N ) = (select count(Lat_N) from station where Lat_N > S.LAT_N))
else (select round(AVG(S.LAT_N),4) from station S where 1 = (select count(Lat_N) from station where Lat_N < S.LAT_N ) - (select count(Lat_N) from station where Lat_N > S.LAT_N))
end from station;

解释

STATION 是表名。LAT_N 是具有数值的列名

假设站表中有 101 条记录(奇数)。这意味着如果表格按 asc 或 desc 排序,则中位数是第 51 条记录。

在上面对 S 表的每个 S.LAT_N 的查询中,我正在创建两个表。一个用于 LAT_N 值的数量小于 S.LAT_N,另一个用于 LAT_N 值的数量大于 S.LAT_N。稍后我将比较这两个表,如果它们匹配,那么我将选择 S.LAT_N 值。当我检查第 51 条记录时,有 50 个值小于第 51 条记录,有 50 条记录大于第 51 条记录。如您所见,两个表中有 50 条记录。所以这就是我们的答案。对于每个其他记录,在两个表中创建用于比较的记录数不同。因此,只有第 51 条记录符合条件。

现在假设站表中有 100 条记录(偶数)。这意味着如果表格按 asc 或 desc 排序,则中位数是第 50 条和第 51 条记录的平均值。

与奇怪的逻辑相同,我正在创建两个表。一个用于 LAT_N 值的数量小于 S.LAT_N,另一个用于 LAT_N 值的数量大于 S.LAT_N。稍后我将比较这两个表,如果它们的差异等于 1,那么我将选择 S.LAT_N 值并找到平均值。当我检查第 50 条记录时,有 49 个值小于第 50 条记录,有 51 条记录大于第 50 条记录。如您所见,两个表中存在 1 条记录的差异。所以这(第 50 条记录)是我们平均的第 1 条记录。同样,当我检查第 51 条记录时,有 50 个值小于第 51 条记录,有 49 条记录大于第 51 条记录。如您所见,两个表中存在 1 条记录的差异。所以这(第 51 条记录)是我们平均的第二条记录。对于每个其他记录,在两个表中创建用于比较的记录数不同。因此,只有第 50 条和第 51 条记录满足条件。

于 2021-06-17T07:28:43.383 回答
1

我在 MySQL 中使用下表作为解决方案:

CREATE TABLE transactions (
  transaction_id int , user_id int , merchant_name varchar(255), transaction_date date , amount int
);

INSERT INTO transactions (transaction_id, user_id, merchant_name, transaction_date, amount)  
VALUES (1, 1 ,'abc', '2015-08-17', 100),(2, 2, 'ced', '2015-2-17', 100),(3, 1, 'def', '2015-2-16', 121),
(4, 1 ,'ced', '2015-3-17', 110),(5, 1, 'ced', '2015-3-17', 150),(6, 2 ,'abc', '2015-4-17', 130), 
(7, 3 ,'ced', '2015-12-17', 10),(8, 3 ,'abc', '2015-8-17', 100),(9, 2 ,'abc', '2015-12-17', 140),(10, 1,'abc', '2015-9-17', 100),
(11, 1 ,'abc', '2015-08-17', 121),(12, 2 ,'ced', '2015-12-23', 130),(13, 1 ,'def', '2015-12-23', 13),(3, 4, 'abc', '2015-2-16', 120),(3, 4, 'def', '2015-2-16', 121),(3, 4, 'ced', '2015-2-16', 121);

计算“金额”列的中位数:

WITH Numbered AS 
(
SELECT *, COUNT(*) OVER () AS TotatRecords,
    ROW_NUMBER() OVER (ORDER BY amount) AS RowNum
FROM transactions
)
SELECT Avg(amount)
FROM Numbered
WHERE RowNum IN ( FLOOR((TotatRecords+1)/2), FLOOR((TotatRecords+2)/2) )
;

TotalRecords = 16 和中位数 = 120.5000

此查询适用于条件,即偶数和奇数记录。

于 2021-09-26T09:14:51.527 回答
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

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

于 2014-07-28T08:33:56.480 回答
0

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

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

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

于 2014-09-02T09:45:09.190 回答
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 与其自身连接并将所有先前的总计聚合到用于确定哪个value 包含指定的百分位数。上述示例计算 p10、p25、p50(中位数)、p75 和 p90。

-克里斯

于 2015-06-17T04:53:16.547 回答
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
于 2015-06-19T12:03:47.473 回答
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 将是所需的结果:-)

于 2015-07-15T10:54:35.470 回答
0

按维度分组的中位数:

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;
于 2015-08-04T16:18:36.020 回答
0

这种方式似乎包括没有子查询的偶数和奇数。

SELECT AVG(t1.x)
FROM table t1, table t2
GROUP BY t1.x
HAVING SUM(SIGN(t1.x - t2.x)) = 0
于 2016-11-01T04:18:47.517 回答
0

这些方法从同一个表中选择两次。如果源数据来自昂贵的查询,这是一种避免运行两次的方法:

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
于 2016-12-08T11:17:28.583 回答
0
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)

或者

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;
于 2017-03-05T01:12:47.370 回答
0

以下 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);

如果您正在寻找详细的解释,请参阅此博客。

于 2017-10-05T05:53:23.917 回答
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));
于 2019-01-22T11:19:14.387 回答
0

下面的查询对于偶数或奇数行都适用。在子查询中,我们正在查找在其前后具有相同行数的值。在奇数行的情况下,有子句将评估为 0(取消符号之前和之后的相同行数)。

类似地,对于偶数行,对于两行(中间的 2 行),having 子句的计算结果为 1,因为它们将(共同)前后具有相同的行数。

在外部查询中,我们将平均出单个值(在奇数行的情况下)或(在偶数行的情况下的两个值)。

select avg(val) as median
from
(
    select d1.val
    from data d1 cross join data d2
    group by d1.val
    having abs(sum(sign(d1.val-d2.val))) in (0,1)
) sub

注意:如果你的表有重复值,上面的有子句应该改为下面的条件。在这种情况下,可能存在原始可能性 0,1 之外的值。以下条件将使此条件动态并在重复的情况下也有效。

having sum(case when d1.val=d2.val then 1 else 0 end)>=
abs(sum(sign(d1.val-d2.val)))
于 2020-11-07T18:14:11.783 回答
0

尝试类似:

SELECT  
CAST (AVG(val) AS DECIMAL(10,4))
FROM
(
    SELECT 
    val,
    ROW_NUMBER() OVER( ORDER BY val ) -1 AS rn,
    COUNT(1) OVER () -1 AS cnt
    FROM STATION
) as tmp
WHERE rn IN (FLOOR(cnt/2),CEILING (cnt/2))

**

注意:-1 的原因是使其索引为零。即行号现在从 0 开始而不是 1

**

于 2021-01-18T10:08:43.603 回答
0

我没有将此解决方案的性能与此处发布的其他答案进行比较,但我发现这是最容易理解的,并且涵盖了用于计算中位数的数学公式的全部范围。换句话说,这个解决方案对于偶数和奇数数据集来说足够健壮:

SELECT CASE 
-- odd-numbered data sets:
WHEN MOD(COUNT(*), 2) = 1 THEN (SELECT median.<value> AS median
FROM
(SELECT t1.<value>
  FROM (SELECT <value>, 
               ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
          FROM <data>) t1,
       (SELECT COUNT(*) AS num_records FROM <data>) t2
 WHERE t1.rownum =(t2.num_records) / 2) as median)
-- even-numbered data sets:
ELSE (select (low_bound.<value> + up_bound.<value>) / 2 AS median
FROM
(SELECT t1.<value>
  FROM (SELECT <value>, 
               ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
          FROM <data>) t1,
       (SELECT COUNT(*) AS num_records FROM <data>) t2
 WHERE t1.rownum =(t2.num_records - 1) / 2) as low_bound,
 (SELECT t1.<value>
  FROM (SELECT <value>, 
               ROW_NUMBER() OVER(ORDER BY <value>) AS rownum
          FROM station) t1,
       (SELECT COUNT(*) AS num_records FROM data) t2
 WHERE t1.rownum =(t2.num_records + 1) / 2) as up_bound)
END
FROM <data>
于 2021-05-31T02:30:31.770 回答
0

在mysql中计算中位数最简单快捷的方法。

select x.col
from   (select lat_n, 
               count(1) over (partition by 'A')        as total_rows, 
               row_number() over (order by col asc) as rank_Order 
        from   station ft) x 
where  x.rank_Order = round(x.total_rows / 2.0, 0) 
于 2021-06-06T06:26:58.753 回答
0

如果这是 MySQL,现在有窗口函数,你可以这样做(假设你想四舍五入到最接近的整数 - 否则只需替换ROUNDCEILorFLOOR或你有什么)。以下解决方案适用于表,无论它们具有偶数行还是奇数行:


WITH CTE AS (
    SELECT val,
            ROW_NUMBER() OVER (ORDER BY val ASC) AS rn,
            COUNT(*) OVER () AS total_count
    FROM data
)
SELECT ROUND(AVG(val)) AS median
FROM CTE
WHERE
    rn BETWEEN
    total_count / 2.0 AND
    total_count / 2.0 + 1;

我认为该线程上的一些最新答案已经采用了这种方法,但似乎人们也想多了,所以认为这是一个改进的版本。无论 SQL 风格如何,没有理由任何人都应该编写包含多个子查询的大段代码来获得 2021 年的中位数。但是,请注意,上述查询仅在被要求找到中位数的情况下才有效连续系列。当然,不管行数如何,有时人们确实会区分连续序列的离散中位数插值中位数。

如果您被要求找到离散系列的中位数并且表格的行数为偶数,则上述解决方案对您不起作用,您应该恢复使用其他解决方案之一,例如 TheJacobTaylor 的解决方案。

下面的第二个解决方案是 TheJacobTaylor 的略微修改版本,我在其中明确声明CROSS JOIN. 这也适用于具有奇数行的表,无论您是否被要求找到连续或离散系列的中位数,但当被要求找到离散系列的中位数时,我会特别使用它。否则,使用第一个解决方案。这样,您永远不必考虑数据是否包含“偶数”或“奇数”个数据点。


SELECT x.val AS median
FROM data x
CROSS JOIN data y
GROUP BY x.val
HAVING SUM(SIGN(1 - SIGN(y.val - x.val))) = (COUNT(*) + 1) / 2;

最后,您可以使用内置函数在 PostgreSQL 中轻松完成此操作。这是一个很好的解释,以及关于离散与插值中位数的有效总结。

https://leafo.net/guides/postgresql-calculating-percentile.html#calculating-the-median

于 2021-06-20T19:38:27.947 回答
0

对于一个表站和列 lat_n,这里是获取中位数的 MySQL 代码:

set @rows := (select count(1) from station);
set @v1 := 0;
set @sql1 := concat('select lat_n into @v1 from station order by lat_n asc limit 1 offset ', ceil(@rows/2) - 1);
prepare statement1 from @sql1;
execute statement1;
set @v2 := 0;
set @sql2 := concat('select lat_n into @v2 from station order by lat_n asc limit 1 offset ', ceil((@rows + 1)/2) - 1);
prepare statement2 from @sql2;
execute statement2;
select (@v1 + @v2)/2;
于 2021-09-23T12:46:01.373 回答
0

您可以使用窗口函数 row_number() 来回答查询以查找介质

select val 
from (select val, row_number() over (order by val) as rownumber, x.cnt 
from data, (select count(*) as cnt from data) x) abc
where rownumber=ceil(cnt/2);
于 2022-02-23T20:15:02.890 回答
-1
set @r = 0;

select  
    case when mod(c,2)=0 then round(sum(lat_N),4)
    else round(sum(lat_N)/2,4) 
    end as Med  
from 
    (select lat_N, @r := @r+1, @r as id from station order by lat_N) A
    cross join
    (select (count(1)+1)/2 as c from station) B
where id >= floor(c) and id <=ceil(c)
于 2016-06-25T10:59:06.787 回答