powered by simpleCommunicator - 2.0.52     © 2025 Programmizd 02
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Как быстро получить рандомные строки?
8 сообщений из 8, страница 1 из 1
Как быстро получить рандомные строки?
    #39987647
Risko
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Получение рандомных строк с помощью ORDER BY random() быстро работает на небольших таблицах, но скорость выполнения падает с ростом количества строк в таблице.

В связи с этим я стал искать решение, которое выбирало бы рандомные строки и работало быстро, как на небольших, так и на огромных таблицах.

Первым делом наткнулся на решение из статьи , отредактировал его под себя так
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
`WITH RECURSIVE r AS (
	  WITH b AS (
	    SELECT
	    min(t.id),
	    (
	      SELECT t.id
	      FROM items AS t
	      WHERE
	        t.sale_price > 100 AND
	        t.sale_price < 2000
	        AND t.views = 0
                AND t.category IN(6, 7)
	      ORDER BY t.id DESC
	      LIMIT 1
	      OFFSET ${custom.numRows} - 1
	    ) max
	    FROM items AS t
	    WHERE 
	      t.sale_price > 100 AND
	      t.sale_price < 2000
	      AND t.views = 0
              AND t.category IN(6, 7)
	  )
	  (
	    SELECT
	      id, min, max, array[]::bigint[] || id AS a, 0 AS n
	    FROM items AS t, b
	    WHERE
	      id >= min + ((max - min) * random())::int AND
	      t.sale_price > 100 AND
	      t.sale_price < 2000
	      AND t.views = 0
              AND t.category IN(6, 7)
	    LIMIT 1
	  ) UNION ALL (
	    SELECT t.id, min, max, a || t.id, r.n + 1 AS n
	    FROM items AS t, r
	    WHERE
	      t.id > min + ((max - min) * random())::int AND
	      t.id <> all( a ) AND
	      r.n + 1 < ${custom.numRows} AND
	      t.sale_price > 100 AND
	      t.sale_price < 2000
	      AND t.views = 0
              AND t.category IN(6, 7)
	    LIMIT 1
	  )
	)
	SELECT * FROM items AS t, r WHERE r.id = t.id`



Работает он очень быстро, но не совсем корректно. Имея таблицу с 3750 строк и значения id идущие подряд до 1183, а затем идет пропуск и далее значения id сразу начинаются с 2084. (строки с пропущенными значениями были удалены). В таком случае запрос намного чаще возвращает строку с id 2084 и 2086 и почти никогда не возвращает строки с id > 3000. (Скрин)
Этот вариант мне не нравится, хоть и работает быстро.

Затем я взял запрос из данной темы, немного переписал его, для получения всей строки по условию. Получилось это
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
`WITH q AS (
WITH RECURSIVE
r AS (
	SELECT array[]::bigint[] AS res, min(id) AS min, max(id)-min(id) AS range, 0 AS n FROM items
	UNION ALL
	SELECT res||ARRAY(SELECT id FROM items WHERE id IN (SELECT (min+range*random())::int FROM generate_series(1,1000)) AND NOT id=ANY(res) AND sale_price > 1000 AND sale_price < 5000 AND views = 0 AND category IN(6), min, range, n+1
	FROM r
	WHERE
	n < ${custom.numRows}+1
	)
    SELECT * FROM (
        SELECT * FROM r ORDER BY array_length(res,1) DESC NULLS LAST LIMIT 1
    ) AS t LIMIT 1
)
SELECT * FROM items WHERE id IN(SELECT unnest(res) FROM q) LIMIT ${custom.numRows}`



custom.numRows = 1; // Количество строк


Данный код конечно работает, но имея в таблице только одну совпадающею с условием строку я получаю её грубо говоря через раз. Т.е выполнил я один раз запрос - получил строку, выполнил второй раз точно такой же запрос - не получил строку, хотя в таблице она есть. Я думаю, что это связано с generate_series(1,1000).

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


Я думал сделать выборку по такому алгоритму:
1) Выбрать все id подходящие под условие.
2) Скриптом (javascript) выбрать рандоное значение из результирующего массива с idшниками.
3) Получить запись со значением id из пункта 2.

Но мне он дико не нравится из-за того, что придется в первом пункте тянуть весь список id из базы. (а если строк миллионы...) Я думаю это сильно скажется на производительность.

Как можно поступить в моём случае? Какие есть варианты? Postgresql v9.5
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987654
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Risko,

если вы можете быстро выбрать "1) Выбрать все id подходящие под условие." то вы и быстро выберете оттуда order by random() limit N
'но скорость выполнения падает с ростом количества строк в таблице.' - не с количеством строк в таблице а с падением скорости выбора всех нужных вам строк.

Т.е. от вашего решения толку ровно ноль... order by random() LIMIT N будет быстрее работать в таком случае.

Чтобы корректно выбрать случайную строку из выборки - сначала надо всю выборку получить, соответственно быстрее чем получение всех подходящих вам строк - это сделать НЕ ВОЗМОЖНО.
Так что никаких вариантов у вас и нет.

--
Maxim Boguk
лучшая поддержка PostgreSQL: dataegret.ru
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987729
ursido
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Risko,

Посмотрите в сторону предложения TABLESAMPLE команды SELECT .
А так же стандартное расширение tsm_system_rows
Это если позволяет версия.

Потом расскажете о результатах.
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987852
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Предлагаю выбирать псевдослучайные строки.
То есть создать индекс по, например, MD5 от первичного ключа:

Код: sql
1.
CREATE INDEX idx_md5_id ON table1(md5(id::text));



Потом выбирать по нему:
Код: sql
1.
2.
3.
4.
5.
SELECT *
FROM table1
WHERE md5(id::TEXT) > md5((SELECT random())::text)
ORDER BY md5(id::TEXT)
LIMIT 5
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987872
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke
Предлагаю выбирать псевдослучайные строки.
То есть создать индекс по, например, MD5 от первичного ключа:

Код: sql
1.
CREATE INDEX idx_md5_id ON table1(md5(id::text));



Потом выбирать по нему:
Код: sql
1.
2.
3.
4.
5.
SELECT *
FROM table1
WHERE md5(id::TEXT) > md5((SELECT random())::text)
ORDER BY md5(id::TEXT)
LIMIT 5



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

--
Maxim Boguk
лучшая поддержка PostgreSQL: dataegret.ru
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987935
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Maxim Boguk
не работает когда надо выбирать по условиям из таблицы (о чем написано в исходном письме)
Это да, дополнительная фильтрация будет снижать производительность.

Однако на миллионе записей довольно быстро.
Код: sql
1.
2.
3.
4.
5.
6.
SELECT id, val, md5(id::TEXT)
FROM (
  SELECT * FROM million ORDER BY md5(id::TEXT)
) AS t
WHERE md5(id::TEXT) > md5((SELECT random())::text) AND val BETWEEN 10000 AND 20000
LIMIT 5;


В поле val случайные значения от 0 до 1000000.
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Limit  (cost=0.45..71.17 rows=5 width=48) (actual time=0.326..0.735 rows=5 loops=1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.01 rows=1 width=8) (actual time=0.001..0.001 rows=1 loops=1)
  ->  Subquery Scan on t  (cost=0.43..38813.15 rows=2744 width=48) (actual time=0.326..0.734 rows=5 loops=1)
        ->  Index Scan using idx_md5_id on million  (cost=0.43..38765.13 rows=3294 width=48) (actual time=0.324..0.730 rows=5 loops=1)
              Index Cond: (md5((id)::text) > md5(($0)::text))
              Filter: ((val >= 10000) AND (val <= 20000))
              Rows Removed by Filter: 569
Planning Time: 0.205 ms
Execution Time: 0.775 ms


При val BETWEEN 100 AND 200:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Limit  (cost=0.45..9685.57 rows=5 width=48) (actual time=3.408..26.726 rows=5 loops=1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.01 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=1)
  ->  Subquery Scan on t  (cost=0.43..38740.95 rows=20 width=48) (actual time=3.407..26.724 rows=5 loops=1)
        ->  Index Scan using idx_md5_id on million  (cost=0.43..38740.60 rows=23 width=48) (actual time=3.406..26.717 rows=5 loops=1)
              Index Cond: (md5((id)::text) > md5(($0)::text))
              Filter: ((val >= 100) AND (val <= 200))
              Rows Removed by Filter: 34637
Planning Time: 0.151 ms
Execution Time: 26.756 ms



Ускорение ведь будет относительно seq scan с рандомом, или я что-то не учёл?
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
Limit  (cost=13833.04..13833.05 rows=5 width=24) (actual time=58.867..58.869 rows=5 loops=1)
  ->  Sort  (cost=13833.04..13857.75 rows=9882 width=24) (actual time=58.866..58.867 rows=5 loops=1)
        Sort Key: (random())
        Sort Method: top-N heapsort  Memory: 25kB
        ->  Gather  (cost=1000.00..13668.91 rows=9882 width=24) (actual time=0.749..61.494 rows=9916 loops=1)
              Workers Planned: 2
              Workers Launched: 2
              ->  Parallel Seq Scan on million  (cost=0.00..11656.00 rows=4118 width=16) (actual time=0.037..22.323 rows=3305 loops=3)
                    Filter: ((val >= 10000) AND (val <= 20000))
                    Rows Removed by Filter: 330028
Planning Time: 0.086 ms
Execution Time: 62.506 ms
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987937
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke,

И опять цитирую автора
"Данный код конечно работает, но имея в таблице только одну совпадающею с условием строку я получаю её грубо говоря через раз. Т.е выполнил я один раз запрос - получил строку, выполнил второй раз точно такой же запрос - не получил строку, хотя в таблице она есть."

оно будет криво работать например на условии where val=10 именно с вышеуказанными симптомами.
...
Рейтинг: 0 / 0
Как быстро получить рандомные строки?
    #39987943
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Maxim Boguk, понял, спасибо.
...
Рейтинг: 0 / 0
8 сообщений из 8, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Как быстро получить рандомные строки?
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]