6

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

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。

4

2 回答 2

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

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

于 2013-03-13T14:09:36.640 回答
1

这是受这篇文章启发的中位数的另一种看法,使用SUBSTRING_INDEXand 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有偶数个元素的情况。pricecreated_at

更新:

值得一提的是,该GROUP_CONCAT函数的默认长度为 1024 字节,请参见此处。这可能会导致很长的结果被截断,从而导致计算错误。如果您担心较大的结果,您可以使用命令SET SESSION group_concat_max_len = N;where is some other,较大的值设置更大的默认值。N我已将该设置添加到上面的代码片段中。我选择了 1000000,但您也可以使用其他值。

COUNT(*)您还可以使用您的值OFFSET之一来抽查您的结果。GROUP BY例如,

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

SELECT COUNT(*) FROM mediana WHERE created_at = '2012-03-06';

  1. 让我们X从第 1 步得到的行数。除以X2 得到一半的值,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;

    这将是您的中值。

于 2016-03-02T16:48:19.193 回答