使用MySQL计算百分位值

时间:2013-11-04 14:34:44

标签: mysql percentile

我有一个包含数千行的表格,我想计算其中一个字段的第90个百分位数,称为“圆形”。

例如,选择第90个百分位的圆值。

我没有看到在MySQL中直接这样做的方法。

有人可以就如何开始这种计算提出一些建议吗?

谢谢!

8 个答案:

答案 0 :(得分:3)

首先,假设您有一个带有值列的表。您希望获得具有第95百分位值的行。换句话说,您正在寻找一个大于所有值的95%的值 这是一个简单的答案:

SELECT * FROM 
(SELECT t.*,  @row_num :=@row_num + 1 AS row_num FROM YOUR_TABLE t, 
    (SELECT @row_num:=0) counter ORDER BY YOUR_VALUE_COLUMN) 
temp WHERE temp.row_num = ROUND (.95* @row_num); 

答案 1 :(得分:2)

比较解决方案:

我的服务器获得130万行的99%所花费的秒数:

  • LIMIT x,y(带索引,无索引):0.01 seconds
  • LIMIT x,y,无处:0.7 seconds
  • LIMIT x,y,其中:2.3 seconds
  • Full scan,无位置:1.6 seconds
  • Full scan,其中:5.7 seconds

使用LIMIT x,y()的大型表的最佳解决方案:

  1. 获取值计数:SELECT COUNT(*) AS cnt FROM t
  2. 获取第n个值,其中n = (cnt - 1) * (1 - 0.95)SELECT k FROM t ORDER BY k DESC LIMIT n,1

该解决方案需要两个查询,因为mysql不支持在LIMIT子句中指定变量,存储过程除外(可以为optimized with stored procedure)。通常,额外的查询开销非常低

如果将索引添加到k列并且不使用复杂的where子句(例如,对于具有100万行的表,则为0.01秒,因为不需要排序),则可以进一步优化此解决方案。

PHP中的实现示例(不仅可以计算列的百分位数,还可以计算表达式的百分位数):

function get_percentile($table, $where, $expr, $percentile) {
  if ($where) $subq = "WHERE $where";
  else $subq = "";

  $r = query("SELECT COUNT(*) AS cnt FROM $table $subq");
  $w = mysql_fetch_assoc($r);
  $num = abs(round(($w['cnt'] - 1) * (100 - $percentile) / 100.0));

  $q = "SELECT ($expr) AS prcres FROM $table $subq ORDER BY ($expr) DESC LIMIT $num,1";
  $r = query($q);
  if (!mysql_num_rows($r)) return null;
  $w = mysql_fetch_assoc($r);
  return $w['prcres'];
}

// Usage example
$time = get_percentile(
  "state", // table
  "service='Time' AND cnt>0 AND total>0", // some filter
  "total/cnt", // expression to evaluate
  80); // percentile

答案 2 :(得分:1)

我试图解决这个问题很长一段时间,然后我找到了以下答案。老实说。即使对于大桌子也很快(我使用它的桌子包含大约5密耳的记录,需要几秒钟)。

SELECT 
    CAST(SUBSTRING_INDEX(SUBSTRING_INDEX( GROUP_CONCAT(field_name ORDER BY 
    field_name SEPARATOR ','), ',', 95/100 * COUNT(*) + 1), ',', -1) AS DECIMAL) 
    AS 95th Per 
FROM table_name;

您可以想象只需将table_name和field_name替换为您的表格和列名称。

有关详细信息,请查看Roland Bouman的原始帖子

答案 3 :(得分:0)

http://www.artfulsoftware.com/infotree/queries.php#68

SELECT  
  a.film_id , 
  ROUND( 100.0 * ( SELECT COUNT(*) FROM film AS b WHERE b.length <= a.length ) / total.cnt, 1 )  
  AS percentile 
FROM film a  
CROSS JOIN (  
  SELECT COUNT(*) AS cnt  
  FROM film  
) AS total 
ORDER BY percentile DESC; 

对于非常大的表格来说,这可能会很慢

答案 4 :(得分:0)

根据perny Tony_Pets的回答,但是正如我在一个类似的问题上所指出的那样:我必须稍稍更改计算,例如,第90个百分位数-“ 90/100 * COUNT(*)+ 0.5”,而不是“ 90/100 * COUNT(*)+1“。有时,它跳过了有序列表中百分比点之后的两个值,而不是为百分比选择下一个更高的值。也许是整数舍入在mysql中的工作方式。

即:

..... SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(fieldValue ORDER BY fieldValue SEPARATOR','),',',90/100 * COUNT(*)+ 0.5),',',-1)为90thPercentile .. ..

答案 5 :(得分:0)

SQL标准为此工作支持PERCENTILE_DISCPERCENTILE_CONT逆分布函数。至少在Oracle,PostgreSQL,SQL Server,Teradata中提供了实现。不幸的是不在MySQL中。 But you can emulate PERCENTILE_DISC in MySQL 8如下:

SELECT DISTINCT first_value(my_column) OVER (
  ORDER BY CASE WHEN p <= 0.9 THEN p END DESC /* NULLS LAST */
) x,
FROM (
  SELECT
    my_column,
    percent_rank() OVER (ORDER BY my_column) p,
  FROM my_table
) t;

这将根据给定PERCENT_RANK的顺序为每一行计算my_column,然后找到百分比排名小于或等于0.9个百分位数的最后一行。

这仅适用于MySQL 8+, which has window function support

答案 6 :(得分:0)

适用于 MySQL 8 的替代解决方案:生成数据的直方图

ANALYZE TABLE my_table UPDATE HISTOGRAM ON my_column WITH 100 BUCKETS;

然后从 information_schema.column_statistics 中选择第 95 条记录:

SELECT v,c FROM information_schema.column_statistics, JSON_TABLE(histogram->'$.buckets', 
     '$[*]' COLUMNS(v VARCHAR(60) PATH '$[0]', c double PATH '$[1]')) hist 
     WHERE column_name='my_column' LIMIT 95,1

瞧!您仍然需要决定是取百分位数的下限还是上限,或者取平均值 - 但现在这是一项小任务。最重要的是 - 一旦构建直方图对象,这将非常快。

此解决方案的功劳:lefred's blog

答案 7 :(得分:0)

在 MySQL 8 中,您可以使用 ntile 窗口函数:

SELECT SomeTable.ID, SomeTable.Round
FROM SomeTable
JOIN (
    SELECT SomeTable, (NTILE(100) OVER w) AS Percentile
    FROM SomeTable
        WINDOW w AS (ORDER BY Round)
) AS SomeTablePercentile ON SomeTable.ID = SomeTablePercentile.ID
WHERE Percentile = 90
LIMIT 1

https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_ntile