Как я должен обрабатывать данные “ранжированные по x из y” в PostgreSQL?

StackOverflow https://stackoverflow.com/questions/1466698

  •  13-09-2019
  •  | 
  •  

Вопрос

У меня есть таблица, для которой я хотел бы иметь возможность представить данные "ранжированный X из Y".В частности, я хотел бы иметь возможность представлять эти данные для отдельной строки относительно эффективным способом (т.е.без выделения каждой строки в таблице).Само ранжирование довольно простое, это прямой ПОРЯДОК по одному столбцу в таблице.

Postgres, по-видимому, создает некоторые уникальные проблемы в этом отношении;AFAICT у него нет RANK или ROW_NUMBER или эквивалентной функции (по крайней мере, в версии 8.3, на которой я застрял на данный момент).Канонический ответ в архивах списка рассылки, по-видимому, заключается в создании временной последовательности и выборе из нее:

test=> create temporary sequence tmp_seq;
CREATE SEQUENCE
test=*> select nextval('tmp_seq') as row_number, col1, col2 from foo;

Похоже, что это решение по-прежнему не поможет, когда я хочу выбрать только одну строку из таблицы (и я хочу выбрать ее по PK, а не по рангу).

Я мог бы денормализовать и сохранить ранг в отдельном столбце, что делает представление данных тривиальным, но просто переносит мою проблему.ОБНОВЛЕНИЕ не поддерживает ORDER BY , поэтому я не уверен, как бы я построил запрос на ОБНОВЛЕНИЕ для установки рангов (за исключением выбора каждой строки и запуска отдельного ОБНОВЛЕНИЯ для каждой строки, что кажется слишком большой активностью БД, чтобы запускать каждый раз, когда ранги нуждаются в обновлении).).

Я упускаю что-то очевидное?Каков правильный способ сделать это?

Редактировать:Очевидно, я выразился недостаточно ясно.Я знаю о СМЕЩЕНИИ / ОГРАНИЧЕНИИ, но я не вижу, как это помогает решить эту проблему.Я не пытаюсь выбрать элемент с X-м рейтингом, я пытаюсь выбрать произвольный элемент (скажем, по его PK), а затем иметь возможность отображать пользователю что-то вроде "занял 43-е место из 312".

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

Решение

Если вы хотите получить звание, сделайте что-то вроде

SELECT id,num,rank FROM (
  SELECT id,num,rank() OVER (ORDER BY num) FROM foo
) AS bar WHERE id=4

Или, если вам действительно нужен номер строки, используйте

SELECT id,num,row_number FROM (
  SELECT id,num,row_number() OVER (ORDER BY num) FROM foo
) AS bar WHERE id=4

Они будут отличаться, когда у вас где-то будут одинаковые значения.Существует также dense_rank(), если вам это нужно.

Конечно, для этого требуется PostgreSQL 8.4.

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

Разве дело не только в этом:

SELECT  *
FROM    mytable
ORDER BY
        col1
OFFSET X LIMIT 1

Или я что-то упускаю?

Обновить:

Если вы хотите показать ранг, используйте это:

SELECT  mi.*, values[1] AS rank, values[2] AS total
FROM    (
        SELECT  (
                SELECT  ARRAY[SUM(((mi.col1, mi.ctid) < (mo.col1, mo.ctid))::INTEGER), COUNT(*)]
                FROM    mytable mi
                ) AS values
        FROM    mytable mo
        WHERE   mo.id = @myid
        ) q

ROW_NUMBER функциональность в PostgreSQL реализована через LIMIT n OFFSET skip.

Редактировать:Поскольку вы просите о ROW_NUMBER() вместо простого ранжирования: row_number() представлен в PostgreSQL в версии 8.4.Так что вы могли бы подумать об обновлении.В противном случае этот обходной путь может быть полезно.

Предыдущие ответы касаются вопроса "выберите все строки и получите их ранг", который не является тем, что вы хотите...

  • вы поссорились
  • вы хотите знать его ранг

Просто делай :

ВЫБЕРИТЕ количество (*) ИЗ таблицы, ГДЕ счет > 1 доллара

Где $ 1 - это оценка строки, которую вы только что выбрали (я полагаю, вы хотели бы отобразить ее, чтобы вы могли выбрать ее ...).

Или сделать :

ВЫБЕРИТЕ a., (ВЫБЕРИТЕ количество() ИЗ таблицы b, ГДЕ оценка > b.оценка) КАК ранг ИЗ таблицы AS a, ГДЕ pk = ...

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

Решение :

ВЫБЕРИТЕ количество (*) ИЗ (ВЫБЕРИТЕ 1 Из таблицы, ГДЕ количество очков > $ 1 ОГРАНИЧЕНО 30)

Вы получите точное ранжирование по 30 лучшим результатам, и это будет быстро.Кого волнуют проигравшие ?

Хорошо, если вы действительно заботитесь о проигравших, вам нужно будет составить гистограмму :

Предположим, счет может варьироваться от 0 до 100, и у вас есть 1000000 проигравших со счетом < 80 и 10 победителей со счетом > 80.

Вы создаете гистограмму того, сколько строк имеют оценку X, это простая маленькая таблица из 100 строк.Добавьте триггер в вашу основную таблицу для обновления гистограммы.

Теперь, если вы хотите ранжировать проигравшего, набравшего балл X, его ранг равен сумме ( histo ), где histo_score > X.

Поскольку ваш счет, вероятно, находится не между 0 и 100, а (скажем) между 0 и 1000000000, вам нужно будет немного подправить его, например, увеличить ячейки гистограммы.таким образом, вам нужно максимум 100 ячеек, или используйте какую-нибудь функцию распределения логарифмической гистограммы.

Кстати, postgres делает это при АНАЛИЗЕ таблицы, поэтому, если вы установите statistics_target равным 100 или 1000 для параметра оценка, ПРОАНАЛИЗИРУЙТЕ, а затем запустите :

ОБЪЯСНИТЕ ВЫБОР * ИЗ таблицы, ГДЕ счет > 1 доллара

вы получите хорошую оценку количества строк.

Кому нужны точные ответы ?

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