Быстрый выбор случайных строк в Postgres

У меня есть таблица в postgres, которая содержит пару миллионов строк. Я проверил в Интернете, и я нашел следующее

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

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

спасибо

Ответ 1

Возможно, вам захочется поэкспериментировать с OFFSET, как в

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

N - количество строк в mytable. Вам может понадобиться сначала выполнить SELECT COUNT(*), чтобы выяснить значение N.

Обновление (Энтони Хатчински)

Здесь вы должны использовать floor:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

Рассмотрим таблицу из 2 строк; random()*N генерирует 0 <= x < 2 и, например, SELECT myid FROM mytable OFFSET 1.7 LIMIT 1; возвращает 0 строк из-за неявного округления до ближайшего int.

Ответ 2

В PostgreSQL 9.5 представлен новый подход для гораздо более быстрого выбора образца: TABLESAMPLE

Синтаксис

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

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

Чтобы избежать медленного COUNT и использовать быстрый TABLESAMPLE для таблиц от 1 строки до миллиардов строк, вы можете сделать:

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

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

Чтобы решить, хотите ли вы использовать BERNULLI oder SYSTEM, прочитайте о разнице в http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/

Ответ 3

Я попробовал это с подзапросом, и он работал нормально. Смещение, по крайней мере, в Postgresql v8.4.4 отлично работает.

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

Ответ 4

Вам нужно использовать floor:

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

Ответ 5

Отметьте эту ссылку для разных вариантов. http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

Обновление: (A.Hatchkins)

Резюме (очень) длинной статьи выглядит следующим образом.

Автор перечисляет четыре подхода:

1) ORDER BY random() LIMIT 1; - slow

2) ORDER BY id where id>=random()*N LIMIT 1 - неравномерно, если имеются пробелы

3) случайный столбец - нужно время от времени обновлять

4) custom случайный агрегат - хитрый метод, может быть медленным: random() необходимо сгенерировать N раз

и предлагает улучшить метод # 2 с помощью

5) ORDER BY id where id=random()*N LIMIT 1 с последующими запросами, если результат пуст.

Ответ 6

Я придумал очень быстрое решение без TABLESAMPLE. Гораздо быстрее, чем OFFSET random()*N LIMIT 1. Он даже не требует подсчета таблиц.

Идея заключается в создании индекса выражения со случайными, но предсказуемыми данными, например md5(primary key).

Вот тест с 1М строками данных:

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

Результат:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

Этот запрос может иногда (с вероятностью 1/Number_of_rows) возвращать 0 строк, поэтому его необходимо проверить и повторить. Также вероятности не совсем то же самое - некоторые строки более вероятны, чем другие.

Для сравнения:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

Результаты сильно различаются, но могут быть довольно плохими:

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

Ответ 7

Самый простой и быстрый способ получить случайную строку - использовать расширение tsm_system_rows:

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

Затем вы можете выбрать точное количество строк, которые вы хотите:

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

Это доступно в PostgreSQL 9.5 и более поздних версиях.

См.: https://www.postgresql.org/docs/current/static/tsm-system-rows.html.