2009-06-29 11 views
17

У меня есть очень большая таблица данных измерений в MySQL, и мне нужно вычислить ранжирование процентиля для каждого из этих значений. У Oracle, похоже, есть функция percent_rank, но я не могу найти ничего подобного для MySQL. Конечно, я мог бы просто перетащить его в Python, который я использую в любом случае для заполнения таблицы, но я подозреваю, что это будет довольно неэффективно, потому что один образец может иметь 200 000 наблюдений.Расчет рейтинга процентиля в MySQL

+0

Не могли бы вы объяснить, что именно вы имеете в виду по процентильному ранга? –

+0

@AssafLavie: http://en.wikipedia.org/wiki/Percentile_rank – eliasah

+0

Я сделал функцию Mysql, работающую для любого процентиля: http://stackoverflow.com/a/40266115/1662956 – dartaloufe

ответ

1

Это относительно уродливый ответ, и я чувствую себя виноватым, говоря это. Тем не менее, это может помочь вам в решении вашей проблемы.

Одним из способов определения процента является подсчет всех строк и подсчет количества строк, которые больше, чем номер, который вы указали. Вы можете рассчитать либо больше, либо меньше, и при необходимости принять обратный.

Создайте индекс на своем номере. total = select count (); less_equal = select count () где value> indexed_number;

процент будет что-то вроде: less_equal/общее или (всего - less_equal)/общее

Убедитесь, что оба они используют индекс, который вы создали. Если это не так, настройте их до тех пор, пока они не станут. В пояснительном запросе должен быть «использование индекса» в правой колонке. В случае выбора count (*) он должен использовать индекс для InnoDB и что-то вроде const для MyISAM. MyISAM будет знать это значение в любое время, не вычисляя его.

Если вам нужно иметь процент, хранящийся в базе данных, вы можете использовать настройку сверху для производительности, а затем вычислить значение для каждой строки, используя второй запрос в качестве внутреннего выбора. Значение первого запроса можно задать как константу.

Помогает ли это?

Jacob

+0

Я действительно пробовал это несколько недель назад, и это было невероятно медленно, поэтому я закончил вычислять процентили в python и поместил значение в базу данных. – lhahne

+0

Вы попытались использовать счетчик выбора (*) и выбрать count (*) <= yourvalue? Вы подтвердили, что оба из них обрабатываются индексом, в котором были только нужные столбцы? Если решение должно было касаться строк данных вообще, я ожидал бы, что он будет на один или два порядка медленнее. Если индексы включали больше требуемых столбцов или конфигурация памяти MySQL не была настроена правильно, это было очень медленно. Если это так, это должно было быть быстрым. Примерно, сколько времени «невероятно медленно»? В зависимости от порядка величины ожидаемого ответа мой ответ может быть очень медленным. – TheJacobTaylor

+0

@ TheJacobTaylor Правильный ответ, но короткий по коду. Если вы установите функциональный запрос типа select select, вы получите мой +1. Кроме того, если вы можете исправить это, вы получите красивый блестящий +1 и проверьте! ;)) http://stackoverflow.com/questions/13689434/update-all-rows-with-countdistinct-only-updates-first-row-the-rest-0 – 2012-12-11 18:35:18

4

нет простого способа сделать это. см http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html

+0

Что я ищу? на самом деле обратное тому, то есть заданное число, оно должно сказать мне его ранг. Я несколько уверен, что это будет проще в Oracle, но, к сожалению, это невозможно. – lhahne

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, поэтому синтаксис может быть неправильным. Кроме того, четкие могут не иметь правильного поведения для того, чего вы хотите достичь. Но это общая идея.
Затем, чтобы получить реальный рейтинг процентилей, вам нужно сначала получить количество значений в переменной (или различные значения в зависимости от соглашения, которое вы хотите принять), и вычислить ранг процентиля с использованием настоящего ранга, указанного выше.

2

Если вы совмещая SQL с процедурным языком, как PHP, вы можете сделать следующее. Этот пример разбивает лишние полетные блоки в аэропорт, в их процентили. Использует предложение LIMIT x, y в MySQL в сочетании с 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"; 
     } 
    } 
} 
18

Вот другой подход, который не требует объединения. В моем случае (таблица с 15 000+) строк он работает примерно через 3 секунды. (Метод JOIN на порядок больше).

В образце, предположит, что меры это столбец, на котором вы вычисление процентов ранга и идентификатора просто идентификатор строки (не обязательно):

SELECT 
    id, 
    @prev := @curr as prev, 
    @curr := measure as curr, 
    @rank := IF(@prev > @curr, @[email protected], @rank) AS rank, 
    @ties := IF(@prev = @curr, @ties+1, 1) AS ties, 
    ([email protected]/@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 

Кредит на это метод идет к Шломи Ноаху. Он пишет об этом подробно здесь:

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

Я проверил это в MySQL и он прекрасно работает; не знаю о Oracle, SQLServer и т. д.

+1

Это работает очень хорошо. Genius SQL. –

+2

К сожалению, это зависит от порядка оценки пользовательских переменных, что является неопределенным поведением. Первый комментарий в этой ссылке цитирует руководство MySQL: «Порядок оценки для пользовательских переменных не определен и может меняться на основе элементов, содержащихся в заданном запросе .... Общее правило никогда не назначать значение для пользовательской переменной в одной части инструкции и использовать одну и ту же переменную в какой-либо другой части того же оператора. Вы можете получить ожидаемые результаты, но это не гарантируется ». Ссылка: http://dev.mysql.com/doc/refman/5.1/ru/user-variables.html – rep

1
SELECT 
    c.id, c.score, ROUND(((@rank - rank)/@rank) * 100, 2) AS percentile_rank 
FROM 
    (SELECT 
    *, 
     @prev:[email protected], 
     @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;