【问题标题】:Count median grouped by day按天分组的计数中位数
【发布时间】:2013-03-13 13:31:42
【问题描述】:

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

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。

【问题讨论】:

    标签: mysql group-by median


    【解决方案1】:

    我希望我没有放纵自己并使事情变得过于复杂,但这是我想出的:

    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
    

    我在这里所做的主要只是在日期更改时将行号重置为 1(按 created_at 排序很重要)并包含日期,以便我们可以按它进行分组。在计算总行数的查询中,我还包括了 created_at,因此我们可以连接两个子查询。

    【讨论】:

    • 谢谢!有用!只有一条评论 - 不应该按 created_at 排序,而是按价格排序,因为数据库中的价格是无序的,并且可以是 2、5、3、6 等;)
    • 是否订购价格并不重要,不是吗?不过对ORDER BY created_at, price也没什么坏处,但重要的是,它先由created_at排序,否则rownum不对。
    • 中值计数非常重要。如果价格是 - 2,3,1 并且订单是按日期排序,您将得到中位数 3,但正确答案是 2。如果没有按日期排序,脚本就可以正常工作。
    • 使用ORDER BY created_at, price 它应该可以正常工作。调整后的答案。
    • 如果我想计算表格子集中的中位数,我应该将 WHERE 子句放在哪里?
    【解决方案2】:

    这是受post 启发使用SUBSTRING_INDEXGROUP_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 值之一来抽查您的结果。例如,

    1. 首先获取特定GROUP BY值的行数,

    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;

      将两个结果平均得到中值。

      b.如果Y 是小数,则将Y 向下舍入到最接近的整数(称为W)并将其用作单个偏移量,

      SELECT price FROM mediana WHERE created_at = '2012-03-06' ORDER BY price LIMIT 1 OFFSET W;

      这将是您的中值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-30
      • 2012-05-10
      • 2013-10-06
      • 2020-08-19
      • 1970-01-01
      • 2018-07-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多