LINUX.ORG.RU

Случайная запись из таблицы в Postgresql

 ,


0

3

Таблица может содержать как десятки миллионов строк, так и всего несколько. Нужно быстро выбрать одну случайную строку.

Что пробовал, и что не получалось:

SELECT * FROM table ORDER BY RAND() LIMIT 1

Не подходит, т.к. просматривает всю таблицу. Слишком долго.

SELECT * FROM table
OFFSET (floor(random() * (SELECT COUNT(*) FROM table)))
LIMIT 1

Тоже долго работает.

SELECT * FROM table WHERE id >= RAND() * 
    ( SELECT MAX (id) FROM table ) 
ORDER BY id LIMIT 1

Благодаря индексам может работать за логарифм. Но не подходит, т.к. нет гарантий, что id будут равномерно распределены (вернее 100% не будут, поскольку данные регулярно добавляются и удаляются).

SELECT * FROM table TABLESAMPLE SYSTEM (N) LIMIT 1

Пока что самый убедительный вариант, работает быстро. Но есть и нюансы. При выставлении большого N рандом становится ужасным. На 1000 сгенерированных записей уникальных было только 200-300 на таблице из десятков миллионов записей. При выставлении же маленького N перестает работать на маленьких таблицах, где только пару сотен строчек записей, просто ничего не возвращает.

Postgres же операционная система, помогите


Ответ на: комментарий от lmde

Я уже оттуда и выписал что не так с предложенными там вариантами

Универсального супер-быстрого варианта, идеального в плане случайности, нет. Просмотрите пару страниц поисковой выдачи и выберите вариант, наиболее подходящий конкретно для вас. Вы не первый с такой проблемой - там полно публикаций именно на эту тему, где рассматриваются все возможные варианты и их «ЗА» и «ПРОТИВ».

vinvlad ★★
()

Когда мне нужен был относительно равномерный семпл делал так:

select * from table where id % 200000 = 4;

У меня работает секунд 30 на таблице со 150 мл записей

masa
()
Ответ на: комментарий от vinvlad

выберите вариант, наиболее подходящий конкретно для вас

Пока не подходит ни один.

Универсального супер-быстрого варианта, идеального в плане случайности, нет

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

lmde
() автор топика

Как идея появилось следующее:

Создать столбец вроде random_id, который следует для каждой строчки заполнять случайным числом от 0 до 1. Создать индекс для быстрого поиска по этому столбцу. При поиске генерировать случайное число от 0 до 1 и искать ближаший random_id

lmde
() автор топика

Но не подходит, т.к. нет гарантий, что id будут равномерно распределены

Можно поддерживать равномерную нумерацию

UPDATE table SET row_id = ROW_NUMBER();

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

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от lmde

Создать столбец вроде

Если приемлемы решения с изменением структуры БД - то, возможно, попробовать ключи сделать в UUIDv4, который по определению рандомный.

Налепил тут себе тестовых таблиц с 200 и 120М записей. Вполне прилично вроде выглядят оба:

SELECT * FROM small_table_uuid t WHERE t.uid > gen_random_uuid() ORDER BY t.uid LIMIT 1;
SELECT * FROM big_table_uuid t WHERE t.uid > gen_random_uuid() ORDER BY t.uid LIMIT 1;

Toxo2 ★★★★
()
Ответ на: комментарий от Toxo2

Упс... Только при слишком малом количестве записей (например всего 2) t.uid > gen_random_uuid() может вообще не оказаться ни одной.

Ну, тогда так, например - гарантировано хоть одна будет (если их там есть):

WITH cte_r AS MATERIALIZED (
	SELECT * FROM small_table_uuid t WHERE t.uid > gen_random_uuid() ORDER BY t.uid LIMIT 1
)
SELECT * FROM cte_r r
UNION ALL
SELECT * FROM small_table_uuid t WHERE NOT EXISTS(SELECT FROM cte_r) LIMIT 1
;

:-)

Toxo2 ★★★★
()
SELECT * FROM table
OFFSET (floor(random() * (SELECT COUNT(*) FROM table)))
LIMIT 1

Вот тут count(*) заменить на примерное количество записей, которое можно выбрать из служебных таблиц. Иначе да, оно перечитывает всю таблицу. Что нибудь типа

SELECT reltuples
  FROM pg_class
 WHERE relname = 'items';

Опять же, offset-limit без указания order by… Ну такое

kardapoltsev ★★★★★
()
Последнее исправление: kardapoltsev (всего исправлений: 2)
Ответ на: комментарий от masa

Ну это все равно минимум в 3 раза быстрее :)

Как вариант where id > randomId limit 1

kardapoltsev ★★★★★
()

Запрос писать не буду, но как идея

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

Loki13 ★★★★★
()

Всем спасибо за ответы. В качестве решения стал использовать UUIDv4 для id

lmde
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.