计算MySQL中的百分位数

时间:2009-06-29 07:41:50

标签: mysql rank percentile

我在MySQL中有一个非常大的测量数据表,我需要为这些值中的每一个计算百分等级。 Oracle似乎有一个名为percent_rank的函数,但我找不到类似MySQL的东西。当然,我可以在Python中强制使用它,但我仍然使用它来填充表格,但我怀疑这样做效率很低,因为一个样本可能有200,000个观察值。

9 个答案:

答案 0 :(得分:19)

这是一种不需要连接的不同方法。在我的情况下(一个包含15,000+的表)行,它在大约3秒内运行。 (JOIN方法需要更长的时间)。

在示例中,假设 measure 是您计算百分比排名的列,而 id 只是行标识符(不是必需的):< / p>

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等。

答案 1 :(得分:4)

答案 2 :(得分:4)

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;

答案 3 :(得分:2)

这是一个相对丑陋的答案,我感到内疚。也就是说,它可以帮助您解决问题。

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

为您的号码创建索引。 total =选择计数(); less_equal = select count()其中value&gt; indexed_number;

百分比如下:less_equal / total或(total - less_equal)/ total

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

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

这有帮助吗?

雅各

答案 4 :(得分: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";
        }
    }
}

答案 5 :(得分:1)

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和窗口函数you can emulate PERCENTILE_DISC, however, again using the PERCENT_RANK and FIRST_VALUE window functions

答案 6 :(得分: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更熟悉,因此语法可能不正确。此外,对于您想要实现的目标,distinct可能没有正确的行为。但这是一般的想法 然后,要获得真正的百分位数,您需要首先获取变量中的值的数量(或根据您要采用的约定的不同值),并使用上面给出的实际等级计算百分位数。

答案 7 :(得分:0)

假设我们有一个销售表,例如:

user_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版本中没有任何功能,这似乎很简单。

答案 8 :(得分:-1)

不确定“百分等级”是什么意思,但是要获取一组值的给定百分位数,请参见http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html sql计算可以很容易地更改为产生另一个或多个百分位数。

一个注意事项:我必须稍微更改计算,例如,第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 ....