这是受post 启发使用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字节,见here。这可能会导致很长的结果被截断,从而导致计算错误。您可以使用命令SET SESSION group_concat_max_len = N; 设置更大的默认值,其中N 是其他一些更大的值,如果您担心较大的结果。我已将该设置添加到上面的代码 sn-p 中。我选择了 1000000,但您也可以使用其他值。
您还可以使用COUNT(*) 和OFFSET 以及您的GROUP BY 值之一来抽查您的结果。例如,
- 首先获取特定
GROUP BY值的行数,
SELECT COUNT(*) FROM mediana WHERE created_at = '2012-03-06';
让X 成为您从第 1 步获得的行数。将X 除以 2 得到其值的一半,Y。
-
使用值Y 作为偏移量来找到中位数。
一个。如果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;
这将是您的中值。