这里有另一种中位数的方法,灵感来自于这个帖子,使用了SUBSTRING_INDEX
和GROUP_CONCAT
。相对于使用行号的@fancyPants所描述的方法,在大表上的性能我不太确定,但在较小的表(约20K行)上,它运行非常快。
SET SESSION group_concat_max_len = 1000000;
SELECT
created_at,
(
CAST(
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(
price ORDER BY price SEPARATOR ','),
',', FLOOR((COUNT(*)+1)/2) ), ',', -1) AS DECIMAL) +
CAST(
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(
price ORDER BY price SEPARATOR ','),
',', FLOOR((COUNT(*)+2)/2) ), ',', -1) AS DECIMAL)
) / 2.0 AS median_price
FROM
mediana
GROUP BY
created_at
;
这是问题中提供的
sqlfiddle的输出结果(该网站似乎出现了故障,但我在MySQL中运行了此表格):
+
| created_at | median_price |
+
| 2012-03-05 | 3.5000 |
| 2012-03-06 | 1.5000 |
+
GROUP_CONCAT
本质上创建了一个字符串表示每个
created_at
日期的价格数组。然后,两个
SUBSTRING_INDEX
命令查找中间值(即中位数)。有必要调用两次
GROUP_CONCAT
并对它们取平均值,以处理单个
created_at
日期的
price
元素数量为偶数的情况。
更新:
值得一提的是,GROUP_CONCAT
函数默认长度为1024字节,请参见这里。这可能会导致非常长的结果被截断,从而导致计算错误。如果您担心结果过大,可以使用命令SET SESSION group_concat_max_len = N;
来设置更大的默认值,其中N
是其他更大的值。我已将该设置添加到上面的代码片段中。我选择了1000000,但您也可以使用其他值。
你还可以使用
COUNT(*)
和
OFFSET
来检查你的结果,其中包括一个
GROUP BY
值。例如,
- 首先获取特定
GROUP BY
值的行数计数,
SELECT COUNT(*) FROM mediana WHERE created_at = '2012-03-06';
令X
为从步骤1中获得的行数。将X
除以2得到其一半值Y
。
使用值Y
作为偏移量来查找中位数。
a. 如果Y
是一个整数,则执行以下两个操作:
SELECT price FROM mediana WHERE created_at = '2012-03-06' ORDER BY price LIMIT 1 OFFSET (Y-1);
和
SELECT price FROM mediana WHERE created_at = '2012-03-06' ORDER BY price LIMIT 1 OFFSET Y;
并对这两个结果求平均值以获取中位数。
b. 如果Y
是一个小数,则将Y
向下舍入到最接近的整数(称其为W
),并将其用作单个偏移量,
SELECT price FROM mediana WHERE created_at = '2012-03-06' ORDER BY price LIMIT 1 OFFSET W;
这将是您的中位数值。
ORDER BY created_at, price
也没有坏处,但重要的是先按created_at排序,否则行号就不对了。 - fancyPantsORDER BY created_at, price
,它应该能够完全正确地工作。已经调整答案。 - fancyPants