Каков наилучший способ найти фиксированное количество СЛУЧАЙНЫХ пользователей в БД на основе параметра? - PullRequest
4 голосов
/ 02 июля 2019

Я занимаюсь разработкой Spring Boot REST API в Котлине. Основной базой данных является Postgresql, и я использую Spring Data JPA для доступа к базе данных.

У меня есть таблица с именем «Пользователи», где у меня есть некоторые пользовательские данные. Одним из свойств пользователя является «пол». Может иметь одно из двух значений: MALE или FEMALE.

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

Итак, теперь я хочу получить 20 случайных пользователей из таблицы «Пользователи», где пол - это MALE, а id - не В [список идентификаторов, которые я видел].

Случайность запроса изначально привела меня к созданию нативного запроса вида:

SELECT *  FROM users WHERE gender = :gender ORDER BY random() LIMIT :number

Однако я понял, что это может быть очень неэффективно, поскольку часть order by random() будет сортировать всю таблицу (или ~ половину таблицы, если я выберу один пол).

Итак, моя вторая идея - позаботиться о случайности в коде. Поэтому я решил сделать вызов db, чтобы подсчитать количество пользователей (чтобы получить самый высокий идентификатор), затем сгенерировать некоторые значения идентификаторов в диапазоне от 0 до самого высокого, отфильтровать те, которые я видел, и затем выбрать пользователей из БД по идентификаторам:

val numberOfUsersInDatabase = userRepository.count()
    val idsOfUsersVotedForBefore = voteService.findIdsOfUsersVotedFor(requestingUser.id!!)
    val excludedIds = idsOfUsersVotedForBefore.plus(requestingUser.id)

    val idsToFetch = random.longs(2*amountOfIds, 1L, numberOfUsersInDatabase)
            .boxed()
            .filter { num -> !excludedIds.contains(num) }
            .limit(amountOfIds)
            .collect(toSet())
   val randomUsers = userRepository.findUsersByIds(idsToFetch)

Но в этом случае у меня нет возможности узнать, какой пол у случайно выбранного пользователя, поэтому у меня нет возможности отфильтровать результаты по полу перед тем, как сделать вызов db.

Подскажите, пожалуйста, как лучше справиться с этим?

Ответы [ 4 ]

2 голосов
/ 02 июля 2019

Я не совсем знаком с языком Kotlin, но я напишу логику на Java и надеюсь, что оттуда она вам пригодится.

Ваша цель состоит в том, чтобы получить 20 из одного пола, но вы не можете выяснить, какой это пол, пока не получите его. Так как мы уже извлекаем 20 строк из базы данных, мы можем получить больше как запасные.

Мы можем использовать совокупное распределение для оптимального подсчета того, сколько строк нам реально нужно, используя этот инструмент: https://stattrek.com/online-calculator/binomial.aspx

Если предположить, что соотношение полов 50/50, вероятность составляет 0,5. Вы можете изменить это, если ваше гендерное распределение отличается для ваших нужд. Если разбивка по полу не 50/50, вы можете создать отдельные выборки для каждого пола, чтобы получить соответствующий уровень успеха. Мы хотим как минимум 20 успешных матчей.

При размере выборки 60 мы получаем 99,6% вероятность того, что у нас будет 20 или более совпадений для пола.

Таким образом, мы можем получить 60 вместо 20, фильтр для первых 20 выбранного пола. Если мы не достигнем 20 (с вероятностью 0,4%), тогда перерисуйте другой набор 20, чтобы заполнить нашу группу. Таким образом, 99% времени, 60 выборок строк, на плохой, вероятно, 80 выборок строк. Это устраняет любое использование RAND на стороне БД, которое должно подходить для очень больших баз данных.

Set<Long> idsToFetch = random.longs(2*amountOfIds, 1L, numberOfUsersInDatabase)
            .boxed()
            .filter { num -> !excludedIds.contains(num) }
            .limit(amountOfIds * 3)
            .collect(toSet());

List<User> randomUsers = userRepository.findUsersByIds(idsToFetch);

List<User> selectedUsers = randomUsers
                            .stream()
                            .filter(e -> e.gender == selectedGender)
                            .limit(amountOfIds)
                            .collect(toList());


if(selectedUsers.length < amountOfIds) { 
    //redo or single fetch operation
}
1 голос
/ 03 июля 2019

выберите 100 тысяч идентификаторов в вашем состоянии. Данные о нескольких МБ в памяти. Просто перетасуйте их. Затем select * from tables in(id1,id2...,id20)

0 голосов
/ 02 июля 2019

Как насчет материализованного представления с запросом в вашем посте. Обновление может быть запланировано по расписанию по вашему выбору (с использованием cron job или, возможно, других инструментов, предлагаемых Postgres)

0 голосов
/ 02 июля 2019

Не генерируйте идентификаторы, вместо этого генерируйте индексы строк.

Тогда в цикле вы можете сделать это

select top 1 start at :randomBase *
from users where gender = :gender
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...