Повторяющаяся разбивка на страницы со случайно упорядоченными рядами

У меня есть 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 и перемешать идентификаторы в памяти на вашем языке программирования.
  • Вы можете создать разные тасования для пользователя или в день