быстрый случайный выбор строк в Postgres - PullRequest
84 голосов
/ 14 марта 2011

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

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

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

спасибо

Ответы [ 7 ]

92 голосов
/ 14 марта 2011

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

43 голосов
/ 15 августа 2015

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

Синтаксис:

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

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

Чтобы избежать медленного 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/

34 голосов
/ 01 августа 2012

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

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;
29 голосов
/ 26 октября 2012

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

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
14 голосов
/ 14 марта 2011

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

Обновление: (А. Хаткинс)

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

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

1) ORDER BY random() LIMIT 1; - медленно

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

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

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

и предлагает усовершенствовать метод # 2, используя

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

3 голосов
/ 25 октября 2015

Я придумал очень быстрое решение без 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)
2 голосов
/ 19 августа 2018

Самый простой и быстрый способ получить случайную строку - использовать расширение 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

...