在 MySQL 中与 MAX、MIN 和 AVG 函数一起使用 MEDIAN

Posted

技术标签:

【中文标题】在 MySQL 中与 MAX、MIN 和 AVG 函数一起使用 MEDIAN【英文标题】:Using MEDIAN along side MAX, MIN, and AVG functions in MySQL 【发布时间】:2013-04-24 06:15:52 【问题描述】:

我有以下运行良好的 mysql 查询:

select 
    count(*) as `# of Data points`, 
    name, 
    max((QNTY_Sell/QNTYDelivered)*1000) as `MAX Thousand Price`,
    min((QNTY_Sell/QNTYDelivered)*1000) as `MIN Thousand Price`,
    avg((QNTY_Sell/QNTYDelivered)*1000) as `MEAN Thousand Price` 
from 
    table_name 
where 
    year(date) >= 2012 and 
    name like "%the_name%" and 
    QNTYDelivered > 0 and 
    QNTY_Sell > 0 
group by name 
order by name;

现在我还希望添加一个结果列,它为我提供每行数据的 MEDIAN。在SELECT 下,这在完美世界中应该是这样的:

median((QNTY_Sell/QNTYDelivered)*1000) as `MEDIAN Thousand Price`

在 Google 中搜索 MySQL 中值函数让我得到了这个答案,如果您对整个表的数据集的中值感兴趣,这似乎没问题:Simple way to calculate median with MySQL

这里的不同之处在于,我是按name 列对表中的数据进行分组,并希望得到按此列分组的每一行数据的中位数。

有谁知道允许我这样做的绝妙技巧吗?

谢谢!

【问题讨论】:

mysql中没有中值函数 必须在同一个查询中吗?如果您可以运行第二个查询,您可以计算集合的中点,因为您知道数据点的数量。中点是一排或两排。运行相同的查询,但添加 LIMIT [midpoint], [midpoint mod 2] 并返回平均值。 如果您执行上述操作,您将不得不更改订单 - 我假设您想要 QNTY_Sell/QNTYDelivered 的中位数,因此您必须对其进行排序才能找到中点。跨度> 【参考方案1】:

即使没有内置中位数函数,您也可以在 MySQL 中使用 GROUP BY 计算中位数。

考虑表格:

Acrington   200.00
Acrington   200.00
Acrington   300.00
Acrington   400.00
Bulingdon   200.00
Bulingdon   300.00
Bulingdon   400.00
Bulingdon   500.00
Cardington  100.00
Cardington  149.00
Cardington  151.00
Cardington  300.00
Cardington  300.00

对于每一行,您可以计算较少的相似项目的数量。您还可以计算有多少值小于或等于:

name        v       <   <=
Acrington   200.00  0   2
Acrington   200.00  0   2
Acrington   300.00  2   3
Acrington   400.00  3   4
Bulingdon   200.00  0   1
Bulingdon   300.00  1   2
Bulingdon   400.00  2   3
Bulingdon   500.00  3   4
Cardington  100.00  0   1
Cardington  149.00  1   2
Cardington  151.00  2   3
Cardington  300.00  3   5
Cardington  300.00  3   5

有查询

SELECT name,v, (SELECT COUNT(1) FROM sale WHERE v<o.v AND name=o.name) as ls
             , (SELECT COUNT(1) FROM sale WHERE v<=o.v AND name=o.name) as lse
  FROM sale o

当小于或等于计数为项目数的一半时,将出现中值

Acrington 有 4 项。其中一半是 2,它在 0..2 范围内(对应于 200.00),也在 2..3 范围内(对应于 300.00)

Bullingdon 也有 4 个项目。 2 在 1..2(值 300.00)和 2..3(值 400.00)范围内

Cardington 有 5 件商品。值 2.5 介于 2 和 3 之间,对应于 Cardington 151。

中值是以下返回的最小值和最大值的平均值:

SELECT cs.name,v
   FROM
   (SELECT name,v, (SELECT COUNT(1) FROM sale WHERE v<o.v AND name=o.name) as ls
                 , (SELECT COUNT(1) FROM sale WHERE v<=o.v AND name=o.name) as lse
      FROM sale o) cs JOIN
   (SELECT name,COUNT(1)*.5 as cn
      FROM sale
      GROUP BY name) cc ON cs.name=cc.name
 WHERE cn between ls and lse

这给出了:

Acrington   200.00
Acrington   200.00
Acrington   300.00
Bulingdon   300.00
Bulingdon   400.00
Cardington  151.00

最后我们可以得到中位数:

SELECT name,(MAX(v)+MIN(v))/2 FROM
(SELECT cs.name,v
   FROM
   (SELECT name,v, (SELECT COUNT(1) FROM sale WHERE v<o.v AND name=o.name) as ls
                 , (SELECT COUNT(1) FROM sale WHERE v<=o.v AND name=o.name) as lse
      FROM sale o) cs JOIN
   (SELECT name,COUNT(1)*.5 as cn
      FROM sale
     GROUP BY name) cc ON cs.name=cc.name
 WHERE cn between ls and lse
 ) AS medians
GROUP BY name

给予

Acrington   250.000000
Bulingdon   350.000000
Cardington  151.000000

【讨论】:

这不会导致二次运行时间吗?您实际上是在要求查询将每个值与其他值进行比较。【参考方案2】:

我发现这样做的唯一方法是通过字符串操作: 使用GROUP_CONCAT 创建所有值的列表,然后使用缩进的SUBSTRING_INDEX 取中值

SELECT
    count(*) AS `# of Data points`,
    name,
    max((QNTY_Sell/QNTYDelivered)*1000) AS `MAX Thousand Price`,
    min((QNTY_Sell/QNTYDelivered)*1000) AS `MIN Thousand Price`,
    avg((QNTY_Sell/QNTYDelivered)*1000) AS `MEAN Thousand Price`
  , CASE (count(*) % 2)
    WHEN 1 THEN SUBSTRING_INDEX(
      SUBSTRING_INDEX(
        group_concat((QNTY_Sell/QNTYDelivered)*1000 
                      ORDER BY (QNTY_Sell/QNTYDelivered)*1000 SEPARATOR ',')
      , ',', (count(*) + 1) / 2)
    , ',', -1)
    ELSE (SUBSTRING_INDEX(
      SUBSTRING_INDEX(
        group_concat((QNTY_Sell/QNTYDelivered)*1000 
                      ORDER BY (QNTY_Sell/QNTYDelivered)*1000 SEPARATOR ',')
      , ',', count(*) / 2)
    , ',', -1)
  + SUBSTRING_INDEX(
      SUBSTRING_INDEX(
        group_concat((QNTY_Sell/QNTYDelivered)*1000 
                      ORDER BY (QNTY_Sell/QNTYDelivered)*1000 SEPARATOR ',')
      , ',', (count(*) + 1) / 2)
    , ',', -1)) / 2
    END median
FROM
    sales
WHERE
    year(date) >= 2012 AND
    name LIKE "%art.%" AND
    QNTYDelivered > 0 AND
    QNTY_Sell > 0
GROUP BY name
ORDER BY name;  

需要 CASE 来检查我们是否有一个具有奇数个值的中值,或两个具有偶数个值的中值,在第二种情况下,中值是所建立的两个值的平均值.

SQLFiddle

【讨论】:

看着这个解决方案的野兽,我真的很喜欢,真的很想知道为什么 SQL 平台集体拒绝实现 Median 和 Mode 函数。有 AVG, MIN, MAX 但没有其他两个是很自私的...... 别误会,我为你的解决方案喝彩。但是,我们不得不提出关于 Dostjewski 作品集大小的查询只是为了得到血腥的中位数,这感觉有点荒谬。我最终来到这里是因为我需要在几个嵌套子查询的上下文中获取中位数和众数;并且意识到要获得像中位数这样看似微不足道的东西是如此困难,这有点令人讨厌。 嘿,我实现了您的解决方案,但注意到偶数个值的中位数“偏移一个单位”,即在 10 个值的数组中,您的解决方案将中位数放置在第 4 和第第 5 个值,而不是第 5 个和第 6 个值。我通过在ELSE 语句的两个SUBSTRING_INDEX 调用中使用(count(*) / 2) + 1 解决了这个问题。

以上是关于在 MySQL 中与 MAX、MIN 和 AVG 函数一起使用 MEDIAN的主要内容,如果未能解决你的问题,请参考以下文章

使用基于 Avg 的 Max 和 Min 来确定最低平均成本和最高平均成本时出错(MySQL)

mysql之count,max,min,sum,avg,celing,floor

26《MySQL 教程》聚合函数(聚合函数 MIN、MAX)

MySQL

Hive分析窗口函数 SUM,AVG,MIN,MAX

来自输入文本文件的 Min Max 和 Avg