13

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

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

我看不到在 MySQL 中执行此操作的简单方法。

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

谢谢!

4

9 回答 9

19

首先,假设您有一个包含值列的表。您想获得第 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); 
于 2016-07-28T19:05:39.693 回答
5

比较解决方案:

在我的服务器上获得 130 万行的 99% 所用的秒数:

  • LIMIT x,y 带索引且无位置:0.01 seconds
  • 限制 x,y 没有哪里:0.7 seconds
  • 限制 x,y,其中:2.3 seconds
  • 全扫描无位置:1.6 seconds
  • 全面扫描,其中: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子句中指定变量,存储过程除外(可以用存储过程优化)。通常额外的查询开销非常低

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

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
于 2020-05-30T14:52:48.107 回答
3

SQL 标准正好支持这项工作PERCENTILE_DISCPERCENTILE_CONT逆分布函数。至少在 Oracle、PostgreSQL、SQL Server、Teradata 中都有实现。不幸的是不在 MySQL 中。但是您可以在 MySQL 8中进行如下模拟:PERCENTILE_DISC

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+

于 2019-01-28T09:41:37.767 回答
1

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; 

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

于 2013-11-04T14:39:24.500 回答
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的原帖

于 2017-08-17T11:43:12.073 回答
1

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

IE:

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

于 2018-07-09T02:59:22.290 回答
1

百分位数最常见的定义是某个分数低于该数字的数字。你可能知道你在一次考试中得到了 67 分(满分 90 分)。但是,除非您知道自己所处的百分位数,否则该数字没有真正的意义。如果你知道你的分数在 95%,那意味着你的分数比参加考试的 95% 的人高。

此解决方案也适用于较旧的 MySQL 5.7。

SELECT *, @row_num as numRows, 100 - (row_num * 100/(@row_num + 1)) as percentile
FROM (
    select *, @row_num := @row_num + 1 AS row_num 
    from (
      SELECT t.subject, pt.score, p.name
      FROM test t, person_test pt, person p, (
        SELECT @row_num := 0
      ) counter 
      where t.id=pt.test_id
      and p.id=pt.person_id
      ORDER BY score desc
    ) temp
) temp2
-- optional: filter on a minimal percentile (uncomment below)
-- having percentile >= 80

内容/记录 数据库设计和关系 示例百分位查询的结果

于 2022-02-22T14:27:32.800 回答
1

在 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

于 2021-08-07T15:48:46.920 回答
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 的博客

于 2021-02-26T18:27:36.870 回答