计算中位数按天分组

时间:2013-03-13 13:31:42

标签: mysql group-by median

我有一个脚本可以计算所有表数据的中值:

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。

2 个答案:

答案 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_INDEXGROUP_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值之一查看结果。例如,

  1. 首先获取特定GROUP BY值的行数
  2. SELECT COUNT(*) FROM mediana WHERE created_at = '2012-03-06';

    1. X为您从步骤1获得的行数。将X除以2得到其值的一半Y

    2. 使用值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;

      这将是您的中值。