Pergunta

Eu tenho uma grande tabela de dados de medição em MySQL e eu preciso calcular o percentil para todos e cada um desses valores. A Oracle parece ter uma função chamada percent_rank mas eu não consigo encontrar nada semelhante para MySQL. Claro que eu poderia apenas força bruta-lo em Python que eu uso qualquer maneira para preencher a tabela, mas eu suspeito que seria muito ineficiente porque uma amostra pode ter 200.000 observações.

Foi útil?

Solução

Esta é uma resposta relativamente feio, e eu me sinto culpado dizendo isso. Dito isto, pode ajudá-lo com seu problema.

Uma maneira de determinar a percentagem seria contar todas as linhas, e contar o número de linhas que são maiores do que o número fornecido. Você pode calcular tanto maior ou menor que e tomar o inverso, se necessário.

Criar um índice em seu número. Total = select count (); less_equal = select count () onde o valor> indexed_number;

A porcentagem seria algo como: less_equal / ou total (total - less_equal) / Total

Certifique-se de que ambos estão usando o índice que você criou. Se eles não são, ajustá-los até que eles são. O explicar consulta deve ter "usando o índice" na coluna da direita. No caso do SELECT COUNT (*) deve estar usando índice para InnoDB e algo como const para MyISAM. MyISAM vai saber este valor, a qualquer momento, sem ter que calculá-lo.

Se você precisava ter a percentagem armazenados no banco de dados, você pode usar a configuração do alto de desempenho e, em seguida, calcular o valor para cada linha usando a segunda consulta como um SELECT interna. o valor do primeiro consulta pode ser definido como uma constante.

Será que isso ajuda?

Jacob

Outras dicas

Aqui está uma abordagem diferente que não requer uma junção. No meu caso (uma tabela com 15.000) linhas, ele é executado em cerca de 3 segundos. (O método de junção leva uma ordem de magnitude mais).

No exemplo, suponha que medida é a coluna na qual você está calculando o posto por cento, e id é apenas um identificador de linha (não obrigatório):

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

O crédito para este método vai para Shlomi Noach. Ele escreve sobre isso em detalhes aqui:

http://code.openark.org/blog/mysql/sql-ranking -sem-autojunção

Eu testei isso no MySQL e ele funciona muito bem; nenhuma idéia sobre a Oracle, SQLServer, etc.

não há nenhuma maneira fácil de fazer isso. consulte http://rpbouman.blogspot.com/2008 /07/calculating-nth-percentile-in-mysql.html

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;

Se você está combinando seu SQL com uma linguagem procedural como PHP, você pode fazer o seguinte. Este exemplo divide vezes bloco vôo excesso em um aeroporto, em seus percentis. Utiliza o X o limite, o ponto Y em MySQL em combinação com ORDER BY. Não é muito bonito, mas faz o trabalho (desculpe lutou com a formatação):

$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";
        }
    }
}

MySQL 8 funções da janela finalmente introduzidos e, entre elas, a PERCENT_RANK() função você estava procurando. Então, basta escrever:

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

A sua questão menciona "percentis", que são uma coisa um pouco diferente. Pelo amor de completude, há PERCENTILE_DISC e PERCENTILE_CONT inversa funções de distribuição no padrão SQL e, em alguns RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), mas não no MySQL. Com funções da janela MySQL 8 e, você pode emular PERCENTILE_DISC, no entanto, novamente usando as funções PERCENT_RANK e janelas FIRST_VALUE .

Para obter a classificação, eu diria que você precisa (esquerda) junção externa a mesa em si mesmo algo como:

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 

Para cada linha, você vai contar quantos (se houver) linhas da mesma tabela possuem um valor inferior.

Note que estou mais familiarizado com sqlserver modo que a sintaxe pode não estar certo. Além disso, o distinto não pode ter o comportamento correto para o que você deseja alcançar. Mas essa é a idéia geral.
Em seguida, para obter o percentil real, você precisa primeiro obter o número de valores em uma variável (ou valores distintos, dependendo da convenção que pretende tomar) e calcular o percentil usando a classificação real dado acima.

Suponha que tenhamos uma tabela de vendas como:

user_id, unidades

então seguinte consulta vai dar percentual de cada usuário:

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 ;

Note que este irá para CROSS JOIN assim resultar em O (n2) a complexidade de modo pode ser considerado como solução unoptimized mas parece simples dado que não têm qualquer função na versão mysql.

Não sei o que o op entende por 'percentil classificação', mas para obter um determinado percentil de um conjunto de valores ver http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html O cálculo sql poderia ser facilmente alterado para produzir um outro ou vários percentis.

Uma nota: eu tive que mudar o cálculo ligeiramente, por exemplo, o 90º percentil - "90/100 * COUNT (*) + 0,5" em vez de "90/100 COUNT * (*) + 1". Às vezes, ele estava pulando dois valores além do ponto percentual na lista ordenada, em vez de pegar o valor mais elevado seguinte para o percentil. Talvez o caminho inteiro arredondamento obras no mysql.

ou seja:

.... SUBSTRING_INDEX (SUBSTRING_INDEX (GROUP_CONCAT (fieldValue POR ORDEM fieldValue SEPARADOR ' '),', ' 90/100 * COUNT (*) + 0,5 ),',', -1) como 90thPercentile ....

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top