Как я должен обрабатывать данные “ранжированные по x из y” в PostgreSQL?
-
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 доллара
вы получите хорошую оценку количества строк.
Кому нужны точные ответы ?