Повторяющаяся разбивка на страницы со случайно упорядоченными рядами
У меня есть API, который возвращает кубические разбитые строки из базы данных. Он работает, однако, когда я заказываю строки с помощью RANDOM()
, я получаю дубликаты на последовательных страницах. Есть ли возможность установить случайное семя на запрос?
Если не удается установить произвольное SEED глобально, чтобы заставить RANDOM()
генерировать одинаковые значения для каждого запроса? Тогда я мог бы просто менять глобальное случайное каждые 3 минуты или что-то в этом роде...
U использует этот код:
SELECT * FROM "table" ORDER BY RANDOM() OFFSET 5 LIMIT 5
Теперь я хочу передать семя в этот запрос, чтобы я мог разбивать случайные результаты.
Я должен сделать это вот так:
SELECT "table".*, SETSEED(0.1) FROM "table" ORDER BY RANDOM() OFFSET 5 LIMIT 5
SELECT "table".*, SETSEED(0.1) FROM "table" ORDER BY RANDOM() OFFSET 10 LIMIT 5
И результаты будут правильно разбиты на страницы?
Ответы
Ответ 1
Если порядок должен быть "перемешан", но не случайным образом...
(Обновление: см. Мой другой ответ для более гибкого и рандомизированного решения.)
Вы говорите "случайный" порядок, который вы получаете при вызове ORDER BY random()
- для каждой строки PostgreSQL вызывает random()
, получает значение и использует его, чтобы решить, как отсортировать эту строку в наборе результатов.
Чтобы сделать это повторяемым, вы должны возиться с семенами. Это выглядит неприлично. Согласно документам:
эффекты будут сохраняться до конца сеанса, если они не будут отменены другим SET
Я думаю, это означает, что при использовании пула соединений setseed
соединение для следующего процесса, который использует это соединение.
Что по модулю?
У меня есть случай, когда мне не нужна настоящая случайность. Мои критерии:
- не один и тот же порядок каждый раз
- предсказуемый порядок на страницах одного и того же набора результатов, чтобы мы не получили дубликаты на последующих страницах
Например, это было бы хорошо:
- Листинг 1
- страница 1: пункты 1, 4
- страница 2: пункты 3, 2
- Перечисление 2 (другой пользователь или тот же пользователь, возвращающийся позже)
- страница 1: пункты 3, 1
- страница 2: пункты 2, 4
Чтобы получить что-то вроде этого, по модулю, кажется, работает хорошо. Например, ORDER BY id % 7, id
для всех страниц запроса 1 и ORDER BY id % 11, id
для всех страниц запроса 2. То есть для каждой строки разделите свой идентификатор по модулю и отсортируйте по остатку. В строках с одинаковым остатком сортируйте по id (чтобы обеспечить стабильность сортировки).
Модуль может быть выбран случайным образом для первой страницы, а затем повторно использован в качестве параметра для каждого последующего запроса страницы.
Вы можете увидеть, как это может работать для вашей базы данных, например:
echo "select id, id % 7 FROM my_table ORDER BY id % 77, id" | psql my_db > sort.txt
Главный модуль, вероятно, даст вам наибольшее изменение. И если ваши идентификаторы начинаются с 1 (чтобы % 77
возвращал первые 77 строк в нормальном порядке), вы могли бы вместо этого попробовать выполнить модуль для поля метки времени. Например:
ORDER BY (extract(epoch from inserted_at)* 100000)::bigint % 77
Но вам нужен индекс функции, чтобы сделать это быстродействующим.
Ответ 2
С помощью этого метода union all
случайный порядок повторяется
select a, b
from (
select setseed(0.1), null as a, null as b
union all
select null, a, b
from t
offset 1
) s
order by random()
offset 0
limit 5
;
Ответ 3
Вы можете использовать setseed(dp)
для семени random()
с семенем в [-1.0, 1.0]. Например:.
engine=> SELECT SETSEED(0.16111981);
setseed
---------
(1 row)
engine=> SELECT RANDOM();
random
-------------------
0.205839179921895
(1 row)
engine=> SELECT RANDOM();
random
-------------------
0.379503262229264
(1 row)
engine=> SELECT RANDOM();
random
-------------------
0.268553872592747
(1 row)
engine=> SELECT RANDOM();
random
-------------------
0.788029655814171
(1 row)
И, конечно же, каждый раз, когда вы перегружаетесь, вы получите тот же результат:
engine=> SELECT SETSEED(0.16111981), RANDOM();
setseed | random
---------+-------------------
| 0.205839179921895
(1 row)
engine=> SELECT SETSEED(0.16111981), RANDOM();
setseed | random
---------+-------------------
| 0.205839179921895
(1 row)
engine=> SELECT SETSEED(0.16111981), RANDOM();
setseed | random
---------+-------------------
| 0.205839179921895
(1 row)
engine=> SELECT SETSEED(0.16111981), RANDOM();
setseed | random
---------+-------------------
| 0.205839179921895
(пояснение: вывод был скопирован из psql
, engine - имя моей базы данных)
Ответ 4
Укажите точные идентификаторы строк (рандомизированные заранее)
Этот запрос даст вам строки с идентификаторами 4, 2, 1 и 4 снова, в этом точном порядке.
SELECT items.id, items.name
FROM items
-- unnest expands array values into rows
INNER JOIN unnest(ARRAY[4,2,1,4]) AS item_id
ON items.id = item_id
доходность
id | name
----+---------------
4 | Toast Mitten
2 | Pickle Juicer
1 | Horse Paint
4 | Toast Mitten
Зная это, вы можете предоставить идентификаторы, которые должны быть включены на каждой странице, как вы хотите.
Например, вы можете SELECT id FROM items ORDER BY random()
, разбить список на "страницы" по 5 идентификаторов каждая и сохранить его в памяти приложения, в Redis или где угодно. Для каждой запрошенной страницы вы должны выполнить запрос выше с правильным блоком из 5 идентификаторов.
Варианты:
- Для истинной случайности вы можете включить
pgcrypto
и ORDER BY gen_random_uuid()
. - Вы можете опустить
ORDER BY
и перемешать идентификаторы в памяти на вашем языке программирования. - Вы можете создать разные тасования для пользователя или в день