我有一个脚本可以计算所有表数据的中值:
SELECT avg(t1.price) as median_val FROM (
SELECT @rownum:=@rownum+1 as `row_number`, d.price
FROM mediana d, (SELECT @rownum:=0) r
WHERE 1
ORDER BY d.price
) as t1,
(
SELECT count(*) as total_rows
FROM mediana d
WHERE 1
) as t2
AND t1.row_number>=total_rows/2 and t1.row_number<=total_rows/2+1;
现在我需要获得不是所有表值的中值,而是按日期分组。可能吗? http://sqlfiddle.com/#!2/7cf27 - 结果我将得到2013-03-06 - 1.5,2013-03-05 - 3.5。
答案 0 :(得分:10)
我希望我没有放松自己并使事情过于复杂,但这就是我想出的:
SELECT sq.created_at, avg(sq.price) as median_val FROM (
SELECT t1.row_number, t1.price, t1.created_at FROM(
SELECT IF(@prev!=d.created_at, @rownum:=1, @rownum:=@rownum+1) as `row_number`, d.price, @prev:=d.created_at AS created_at
FROM mediana d, (SELECT @rownum:=0, @prev:=NULL) r
ORDER BY created_at, price
) as t1 INNER JOIN
(
SELECT count(*) as total_rows, created_at
FROM mediana d
GROUP BY created_at
) as t2
ON t1.created_at = t2.created_at
WHERE 1=1
AND t1.row_number>=t2.total_rows/2 and t1.row_number<=t2.total_rows/2+1
)sq
group by sq.created_at
我在这里所做的,主要是在日期改变时将rownumber重置为1(通过created_at进行排序很重要)并包含日期,以便我们可以按日期进行分组。在计算总行数的查询中,我还包括created_at,因此我们可以加入两个子查询。
答案 1 :(得分:1)
以下是使用SUBSTRING_INDEX
和GROUP_CONCAT
启发此post的中位数的另一种观点。我不确定相对于使用行号的@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
进行两次调用,并对它们进行平均处理,以处理单个price
日期中偶数created_at
个元素的情况。
更新:
值得一提的是GROUP_CONCAT
函数的默认长度为1024字节,请参阅here。这可能导致很长的结果被截断,这将导致错误计算。您可以使用命令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
作为偏移量来查找中位数。
一个。如果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;
并将两个结果取平均值以得到中值。
湾如果Y
是小数,则将Y
向下舍入到最接近的整数(称为W
)并将其用作单个偏移量,
SELECT price FROM mediana WHERE created_at = '2012-03-06' ORDER BY price LIMIT 1 OFFSET W;
这将是您的中值。