Вопрос

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

Это было полезно?

Решение

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

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

Создайте индекс для вашего номера.итого = количество выбранных ();less_equal = выбрать количество() где значение > индексируемое число;

Процентное соотношение было бы примерно таким:less_equal / итого или (итого - less_equal)/всего

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

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

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

Джейкоб

Другие советы

Вот другой подход, который не требует объединения.В моем случае (таблица с более чем 15 000 строками) она выполняется примерно за 3 секунды.(Метод ОБЪЕДИНЕНИЯ занимает на порядок больше времени).

В примере предположим, что измерять является столбцом, по которому вы вычисляете процентный ранг, и 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

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

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

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

простого способа сделать это не существует.видишь 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;

Если вы объединяете свой 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";
        }
    }
}

MySQL 8 наконец-то представил оконные функции, и среди них PERCENT_RANK() функция, которую вы искали.Итак, просто напишите:

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

В вашем вопросе упоминаются "процентили", которые немного отличаются друг от друга.Для полноты картины, существуют PERCENTILE_DISC и PERCENTILE_CONT обратное распределение функционирует в стандарте SQL и в некоторых RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), но не в MySQL.С MySQL 8 и оконными функциями, вы можете подражать PERCENTILE_DISC, однако, снова используя PERCENT_RANK и FIRST_VALUE функции окна.

Чтобы получить ранг, я бы сказал, что вам нужно (слева) внешнее объединение таблицы само по себе что-то вроде :

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

Предположим, у нас есть таблица продаж, подобная :

идентификатор пользователя, единицы измерения

затем следующий запрос выдаст процентиль каждого пользователя :

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.

Не уверен, что op подразумевал под "процентильным рангом", но чтобы получить заданный процентиль для набора значений, см. http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html Вычисление sql может быть легко изменено для получения другого или нескольких процентилей.

Одна нота:Мне пришлось немного изменить расчет, например, 90-й процентиль - "90/100 * COUNT (*) + 0,5" вместо "90/100 * COUNT (*) + 1".Иногда это приводило к пропуску двух значений после точки процентиля в упорядоченном списке вместо выбора следующего более высокого значения для процентиля.Возможно, так работает целочисленное округление в mysql.

ie:

....SUBSTRING_INDEX(ПОДСТРОЧНЫЙ ИНДЕКС( GROUP_CONCAT(ПОРЯДОК значений полей По РАЗДЕЛИТЕЛЮ значений полей ','), ',', 90/100 * КОЛИЧЕСТВО (*) + 0.5), ',', -1) как 90 - й процентиль ....

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top