用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。
按维度分组的中位数:
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;
在阅读了所有之前的内容后,它们与我的实际需求不匹配,所以我实现了自己的一个不需要任何过程或复杂的语句,只是我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
我希望这能对一些人有用,就像这个网站上的许多其他评论对我一样。
这些方法从同一个表中选择两次。如果源数据来自一个昂贵的查询,这是一种避免运行两次的方法:
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
如果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组。
Rob
不幸的是,无论是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个简单步骤:
将上面代码中的“median_table”(出现2次)替换为您的表名
将“median_column”(3次)替换为您希望为其查找中位数的列名
如果你有一个WHERE条件,用WHERE条件替换“WHERE 1”(2次)
我刚刚在网上的评论中找到了另一个答案:
对于几乎所有SQL中的中位数:
SELECT x.val from data x, data y
GROUP BY x.val
总和(符号(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
这将返回您想要的值的一行。