23

我在 MySQL 中有一个非常大的测量数据表,我需要为这些值中的每一个计算百分位等级。Oracle 似乎有一个名为 percent_rank 的函数,但我找不到与 MySQL 类似的任何东西。当然,我可以在 Python 中对它进行暴力破解,无论如何我都会使用它来填充表格,但我怀疑这会非常低效,因为一个样本可能有 200.000 个观察值。

4

8 回答 8

20

这是一种不需要加入的不同方法。在我的情况下(一个超过 15,000 行的表),它运行大约 3 秒。(JOIN 方法需要一个数量级的时间)。

在示例中,假设measure是您计算百分比排名的列,并且id只是一个行标识符(不是必需的):

SELECT
    id,
    @prev := @curr as prev,
    @curr := measure as curr,
    @rank := IF(@prev > @curr, @rank+@ties, @rank) AS rank,
    @ties := IF(@prev = @curr, @ties+1, 1) AS ties,
    (1-@rank/@total) as percentrank
FROM
    mytable,
    (SELECT
        @curr := null,
        @prev := null,
        @rank := 0,
        @ties := 1,
        @total := count(*) from mytable where measure is not null
    ) b
WHERE
    measure is not null
ORDER BY
    measure DESC

这种方法的功劳归功于 Shlomi Noach。他在这里详细描述了它:

http://code.openark.org/blog/mysql/sql-ranking-without-self-join

我已经在 MySQL 中对此进行了测试,效果很好;不知道 Oracle、SQLServer 等。

于 2011-10-25T03:18:16.533 回答
6
SELECT 
    c.id, c.score, ROUND(((@rank - rank) / @rank) * 100, 2) AS percentile_rank
FROM
    (SELECT 
    *,
        @prev:=@curr,
        @curr:=a.score,
        @rank:=IF(@prev = @curr, @rank, @rank + 1) AS rank
    FROM
        (SELECT id, score FROM mytable) AS a,
        (SELECT @curr:= null, @prev:= null, @rank:= 0) AS b
ORDER BY score DESC) AS c;
于 2015-04-20T07:01:50.153 回答
4

没有简单的方法可以做到这一点。见http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html

于 2009-06-29T07:58:08.877 回答
3

这是一个比较难看的答案,说出来我感到内疚。也就是说,它可能会帮助您解决问题。

确定百分比的一种方法是计算所有行数,并计算大于您提供的行数的行数。您可以计算大于或小于,并根据需要取反。

在您的号码上创建索引。总计 = 选择计数();less_equal = select count( ) where value > indexed_number;

百分比类似于:less_equal / total 或 (total - less_equal)/total

确保它们都在使用您创建的索引。如果不是,请调整它们直到它们是。解释查询应该在右侧列中有“使用索引”。在 select count(*) 的情况下,它应该使用 InnoDB 的 index 和 MyISAM 之类的东西。MyISAM 将随时知道这个值,而无需计算它。

如果您需要将百分比存储在数据库中,您可以使用上面的设置来提高性能,然后使用第二个查询作为内部选择来计算每一行的值。第一个查询的值可以设置为常量。

这有帮助吗?

雅各布

于 2009-08-31T06:09:33.557 回答
2

如果您将 SQL 与 PHP 等过程语言相结合,您可以执行以下操作。此示例将多余的航班阻塞时间分解为机场的百分位数。将 MySQL 中的 LIMIT x,y 子句与ORDER BY. 不是很漂亮,但可以完成工作(对不起,格式问题):

$startDt = "2011-01-01";
$endDt = "2011-02-28";
$arrPort= 'JFK';

$strSQL = "SELECT COUNT(*) as TotFlights FROM FIDS where depdt >= '$startDt' And depdt <= '$endDt' and ArrPort='$arrPort'";
if (!($queryResult = mysql_query($strSQL, $con)) ) {
    echo $strSQL . " FAILED\n"; echo mysql_error();
    exit(0);
}
$totFlights=0;
while($fltRow=mysql_fetch_array($queryResult)) {
    echo "Total Flights into " . $arrPort . " = " . $fltRow['TotFlights'];
    $totFlights = $fltRow['TotFlights'];

    /* 1906 flights. Percentile 90 = int(0.9 * 1906). */
    for ($x = 1; $x<=10; $x++) {
        $pctlPosn = $totFlights - intval( ($x/10) * $totFlights);
        echo "PCTL POSN for " . $x * 10 . " IS " . $pctlPosn . "\t";
        $pctlSQL = "SELECT  (ablk-sblk) as ExcessBlk from FIDS where ArrPort='" . $arrPort . "' order by ExcessBlk DESC limit " . $pctlPosn . ",1;";
        if (!($query2Result = mysql_query($pctlSQL, $con)) ) {
            echo $pctlSQL  . " FAILED\n";
            echo mysql_error();
            exit(0);
        }
        while ($pctlRow = mysql_fetch_array($query2Result)) {
            echo "Excess Block is :" . $pctlRow['ExcessBlk'] . "\n";
        }
    }
}
于 2011-03-25T05:21:30.683 回答
2

MySQL 8 终于引入了窗口函数,其中就有PERCENT_RANK()你要找的函数。所以,只需写:

SELECT col, percent_rank() OVER (ORDER BY col)
FROM t
ORDER BY col

您的问题提到了“百分位数”,这是一个略有不同的东西。为了完整起见,SQL 标准和某些 RBDMS(Oracle、PostgreSQL、SQL Server、Teradata)中存在PERCENTILE_DISCPERCENTILE_CONT分布函数,但 MySQL 中没有。使用 MySQL 8 和窗口函数,您可以模拟PERCENTILE_DISC,但是,再次使用PERCENT_RANKFIRST_VALUE窗口函数

于 2019-01-28T09:36:34.337 回答
0

要获得排名,我会说您需要(左)外部加入表格本身,例如:

select t1.name, t1.value, count(distinct isnull(t2.value,0))  
from table t1  
left join table t2  
on t1.value>t2.value  
group by t1.name, t1.value 

对于每一行,您将计算同一个表中有多少(如果有)行具有较低的值。

请注意,我对 sqlserver 更熟悉,因此语法可能不正确。此外,不同的可能没有您想要实现的正确行为。但这是一般的想法。
然后要获得真正的百分位排名,您需要首先获取变量中值的数量(或不同的值,具体取决于您要采用的约定),并使用上面给出的实际排名计算百分位排名。

于 2009-08-21T08:39:00.870 回答
0

假设我们有一个如下的销售表:

用户 ID,单位

然后以下查询将给出每个用户的百分位数:

select a.user_id,a.units,
(sum(case when a.units >= b.units then 1 else 0 end )*100)/count(1) percentile
from sales a join sales b ;

请注意,这将用于交叉连接,因此会导致 O(n2) 复杂度,因此可以视为未优化的解决方案,但鉴于我们在 mysql 版本中没有任何功能,这似乎很简单。

于 2018-11-15T14:17:26.100 回答