用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 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
ORDER BY grp_field, d.val
)作为t1 JOIN (
SELECT grp_field, count(*)为total_rows
数据d
GROUP BY grp_field
)为t2
在t1。Grp_field = t2.grp_field
在t1.row_number =地板(total_rows / 2) + 1;
下面的查询对于奇数行和偶数行都非常有效。在子查询中,我们正在寻找前后行数相同的值。对于奇数行的情况,having子句的值将为0(前后相同的行数将抵消符号)。
类似地,对于偶数行,having子句对于两行(中间的两行)的计算结果为1,因为它们(总的来说)前后的行数相同。
在外层查询中,我们将平均出单个值(奇数行)或(偶数行2个值)。
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
注意:如果你的表有重复的值,上面的having子句应该更改为下面的条件。在这种情况下,可能有一些值超出了原来的可能性(0,1)下面的条件将使这个条件动态,并在重复的情况下工作。
having sum(case when d1.val=d2.val then 1 else 0 end)>=
abs(sum(sign(d1.val-d2.val)))
我下面提出的解决方案只需要一个查询,而不需要创建表、变量甚至子查询。
另外,它可以让你在组-查询(这是我需要的!)
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
我的代码,高效,没有表或额外的变量:
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;