Один из моих коллег спросил меня, можно ли получить 2 случайные строки из сгруппированной таблицы, используя всего один запрос. Я принял этот вопрос как вызов. После нескольких попыток я смог закончить запрос.
В следующем примере мы используем две таблицы, определенные следующим образом.
Предположим, у нас есть структура таблицы, как показано ниже.
+----+-------+----------+ | id | name | group_id | +----+-------+----------+ | 1 | Alice | 1 | | 2 | Bob | 2 | | 3 | Mark | 2 | | 4 | Fred | 1 | | 5 | John | 1 | | 6 | Jack | 2 | | 7 | Amy | 2 | | 8 | Ozgur | 1 | +----+-------+----------+
Запрос, который мы напишем, должен каждый раз получать случайные имена из группы 1, группы 2. Первые 2 результата этого запроса показаны ниже в качестве примера.
#1 +----+-------+-----------+ | id | name | group_id | +----+-------+-----------+ | 5 | John | 1 | | 8 | Ozgur | 1 | | 6 | Jack | 2 | | 3 | Mark | 2 | +----+-------+-----------+ #2 +----+-------+-----------+ | id | name | group_id | +----+-------+-----------+ | 4 | Fred | 1 | | 1 | Alice | 1 | | 3 | Mark | 2 | | 7 | Amy | 2 | +----+-------+-----------+
Сначала я пошагово напишу sql-запрос, а затем попытаюсь его объяснить.
Моя основная идея заключалась в сортировке записей по group_id, но в случайном порядке. Чтобы реализовать эту идею, я создал случайный ранг с помощью group_id + ‘-’ + случайное число и назвал его rand_rank в подзапросе. Я использую hypen, поэтому ранг rand становится строковым, а сортировка всегда упорядочивается по group_id.
select id, name, group_id, CONCAT(group_id, '-', round(rand() * 100)) as rand_rank from names order by rand_rank
Вот результат запроса. Каждый раз результаты будут разными из-за rand_rank.
+----+-------+-----------+-----------+ | id | name | group_id | rand_rank | +----+-------+-----------+-----------+ | 4 | Fred | 1 | 1-15 | | 1 | Alice | 1 | 1-20 | | 8 | Ozgur | 1 | 1-31 | | 5 | John | 1 | 1-73 | | 6 | Jack | 2 | 2-19 | | 3 | Bob | 2 | 2-19 | | 3 | Mark | 2 | 2-34 | | 7 | Amy | 2 | 2-78 | +----+-------+-----------+------------
Теперь я могу присвоить каждой записи новый рейтинг.
select id, name, group_id, @group_rank := IF(@current_group=group_id, @group_rank + 1, 1) as group_rank, @current_group := group_id from ( select id, name, group_id, CONCAT(group_id, '-', round(rand() * 100)) as rand_rank from names order by rand_rank ) tmp
И, наконец, достаточно использовать условие where, применяя вновь созданный group_rank с количеством записей, которые нужно отобразить.
select id, name, group_id from ( select id, name, group_id, @group_rank := IF(@current_group=group_id, @group_rank + 1, 1) as group_rank, @current_group := group_id from ( select id, name, group_id, CONCAT(group_id, '-', round(rand() * 100)) as rand_rank from names order by rand_rank ) tmp ) ranked where group_rank <= 2 order by group_id;
Я пробовал этот запрос с 500 группами x 50000 именами и не обнаружил никаких проблем с производительностью. Хотя это может создать некоторые проблемы с производительностью при работе с большими таблицами.