Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Случайная выборка записей mysql


Как можно организовать случайный выбор записей из таблицы, посредством одного запроса?

P.S.: Имеется таблица статей, а также сводная таблица (статьи на тему), со ссылко
на первую таблицу. Нужно добавить в сводную таблицу N записей, указателей на статьи случайным образом. 

Можно ли как-то случайно выбирать строки из таблицы в запросе?
    


Ответы

Ответ 1



Если размер таблицы статей articles не велик, можно отсортировать записи в случайно порядке, например, при помощи конструкции ORDER BY RAND(). Вставить записи в сводную таблицу theme_articles можно при помощи запроса INSERT ... SELECT ... INSERT INTO theme_articles SELECT * FROM articles ORDER BY RAND() LIMIT 2; К сожалению, у конструкции ORDER BY RAND() печальная слава, так как чаще всего он означает сортировку данных на жестком диске. Уменьшить вероятность этого события можн уменьшив размер промежуточной таблицы. Даже если EXPLAIN сообщает о сортировке в файле, при небольшом размере таблицы, высока вероятность, что сортировка пройдет все-таки в оперативной памяти (особенно, если значение директивы tmp_table_size достаточно высоко - несколько десятков мегабайт). В любом случае имеет смысл сортировать в случайном порядке не всю таблицу, а лишь первичные ключи - это уменьшит размер сортируемой промежуточной таблицы INSERT INTO theme_articles SELECT m.* FROM (SELECT id FROM articles ORDER BY RAND() LIMIT 2) AS r LEFT JOIN articles AS m USING(id); Такой вариант подойдет уже для средних таблиц. Если же у вас имеется возможност выбрать первичные ключи в случайном порядке вне СУБД - это самый идеальный вариант случае гигантских таблиц (достигающих гигабайтных размеров). Как правило, в таких случая СУБД используют совместно с каким-нибудь NoSQL-решением, полностью расположенным в оперативной памяти. Можно попробовать извлечь идентификаторы случайных статей из него (что заведомо быстрее MySQL завязанной на транзакции и жесткий диск) и воспользоваться первым запросом, задействовав для выбора статей конструкцию IN INSERT INTO theme_articles SELECT * FROM articles WHERE id IN (252, 1024); Запросы выше предполагают, что структура таблиц идентичная, первичный ключ имее имя id, а количество вставляемых случайных записей равно 2.

Ответ 2



Данная задача сводится к получению N случайных ID статей. Когда они получены на основе них выбирается любая информация по статьям и вставляется в целевую таблицу. Если таблица не очень большая, то случайные ID выбираются простым запросом (тут и далее таблицу со статьями будем называть randTest и выбирать N=10 записей): select ID from randTest order by rand() limit 10 Решение способно дать нужное количество статей со случайными, хорошо распределенными значениями ID. К сожалению данное решение вынуждено выбрать все существующие ID из индекс и отсортировать их случайным образом. При большом количестве записей, удовлетворяющих условиям поиска статей в исходной таблице это накладно. На тестовых данных в 1.2 миллиона записей данный запрос отработал за 0.601009 сек. Если работа разовая, этим можно пренебречь. Но что, если подобный запрос надо выполнять часто ? Следующим, по распространенности способом получать случайные записи является запро заранее получающий нужное случайное число и ищущий запись с ID равную или большую вычисленному: select id from randTest, (select ceil(min_id+rand()*id_cnt) rnd from (select max(id)-min(id) id_cnt,min(id) min_id from randTest) A ) N where id>=N.rnd limit 1 Полученный запрос предусматривает, что в таблице ID могут начинаться не с 1 и нумераци может идти с "дырками". Например, в моей тестовой таблице минимальный ID=1, максимальны 2322674, при том что записей всего 1245191, для чистоты эксперимента оставлены дырк в нумерации более 100к ID подряд и между всеми существующими ID оставлен зазор в 5-30 номеров, что эмулирует таблицу с большим удаленным блоком старых данных. Столь сложная вложенность подзапросов к сожалению потребовалась из за того, что MySQL некорректно воспринимал непосредственное id>=(rand()*min()...) и выдавал только записи из первых тысяч. Но у данного запроса два существенных ограничения - он может выдавать только по одно записи и при многократном использовании он будет выдавать ID находящиеся сразу за большими "дырками" в данных гораздо чаще, чем другие значения. Т.к. если у нас нет записей от 0 до 100к, то любое значение rand() меньшее 100к будет давать запись с id 100001. Попробуем решить эти две проблемы. Во первых, что бы была возможность генерироват N случайных значений нам нужна таблица с N записей. Назовем ее seqnum, ее наполнени не имеет роли, например там будут записи с единственным полем со значениям 1,2,3,.. Во вторых, нам надо решать проблему дыр. Для этого мы перестанем искать записи большие найденного числа, а будем брать записи строго равные ему. Правда в таком случае часть случайных чисел не дадут записей на выходе. Эту проблему мы решим просто увеличив количество получаемых чисел с хорошим запасом, так что добавим в seqnum скажем 8*N записей. select id from randTest, (select ceil(min_id+rand()*id_cnt) rnd from seqnum, (select max(id)-min(id) id_cnt,min(id) min_id from randTest) N ) A where id=rnd limit 10 Запрос дает стабильно 10 случайных ID за 0.001446 сек. Что в 400 раз быстрее orde by rand() на 1.2M записей. Обращаю внимание, что получение max(id) и min(id) в MySQ работают практически мгновенно, т.к. получаются непосредственно из начала и конца индекс и их прямое применение в запросе вполне оправдано. Данный метод применим к боле мене нормально наполненным таблицам. Если же у вас в таблице на 100 записей задействовано 100000 ID, то для получения 1 случайной записи потребуется перепробовать более 1000 случайных чисел. В таких случаях может помочь только генерация дополнительной таблицы в которой с разрозненными ID основной таблицы будут сопоставлены подряд идущие номера записей.

Ответ 3



SELECT id FROM table WHERE cond = '0' and id > FLOOR(1 + RAND() * (SELECT MAX(id) FROM table)) LIMIT 1 Ну и JOIN не забыть.

Ответ 4



Если записей не много, то ORDER BY RAND в помощь. Иначе, выбирайте все в массив array_shuffle() и потом берите с него 5 элементов.

Ответ 5



SELECT * FROM mytable ORDER BY RAND() LIMIT 4

Ответ 6



Пускай есть таблица: Create table articles (id number, txt text) где id - это ИД статьи, а articles это сама статья. Можно попробовать упорядочивать статьи по хэшу MD5(ID||to_char(SYSDATE(), 'yyyymmddhh24miss')). Благодаря уникальности времени, хэши каждый раз должны получатся разные. То есть получается что-то в духе: select ID from articles order by MD5(ID||to_char(SYSDATE(), 'yyyymmddhh24miss')) LIMIT 2; Вместо мд5 можно использовать любую хэш функцию, в том числе и достаточно простую Не берусь сказать, как быстро будет работать подобное решение. Это просто способ взглянуть на проблему с другой стороны, не используя Order by Rand()

Ответ 7



Можно попробовать так Пускай есть таблица: Create table articles (id number, txt text, num number) де id - это ИД статьи, articles это сама статья, num - это номер строки по порядку(от 0 до n без пропусков). По полю num строим уникальный индекс. Задумка такова: получить порядковый номер случайно строки и выбрать ее. Таким образом случайная статья будет селектиться так: select * from articles where num in (select round(rand()*count(1), 0) from articles) Из недостатков, если надо много случайных статей, то запрос придется выполнять несколько раз. В теории при таком подходе удастся избежать тяжелых сортировок с использованием Rand. Развивая тему, можно добавить пару настроечных таблиц. Первая будет знать, скольк всего статей в Articles, чтобы не пробегать весь набор данных каждый раз. Например инкрементально обновлять значение тригером при добавлении новой строки. А вторая таблица будет содержать необходимое кол-во строк для получения нужного набора из рандомных статей. Пусть есть таблица count_articles с одной строкой и колонкой cnt содержащей кол-в строк в articles и таблица table2 содержащая столько строк, сколько надо случайных статей. Тогда запрос 10 случайных статей может выглядеть примерно так: select * from articles where num in (select round(rand()*c.cnt, 0) from count_articles cnt join table2 on 1=1)

Ответ 8



Почему минусы - обясннте что в ответе неправильно. Только не нужно писать "все не правильно". Конкретно что не так? Может операции с массивами очень реурсоемкие. Сегодня утром почему-то меня "пробило" на решение этой "проблемы". Загрузить ВСЕ идСтрок в массив. В моем решении это, наверное, единственный недостаток, потому что если таблица состоит из многих миллиардов строк, то ОП не хватит для массива. 1.1 Выборку делаем НЕ в цикле! Простой селект select capid from tbl; Делаем массиву array_shufle(). И после этого $rnd_rows=array_slice($mass, 0, КоличесвтоРндСтрок) 3.1 КоличествоРндСтрок может быть аргументом функции, написанной вами, для получения случайных строк. Проверки на количество нужных рндЗаписей, и на кол-во элементов в массиве (т е строк в таблице) сами сможете реалиовать. Описал решение "проблемы" без примеров кода, для того чтобы вы сами внесли нечт свое в реализацию этой "очень сложной проблемы" %:)). Если массив очень огромный, незабываем делать ему unset(). Зачем столько мучаться, придумывать какие-то многострочные (многостраничные) хитры запросы? Даже на жабре (в том числе и на этом ресурсе решения нет. Если бы оно был то не было бы извращений с многострочными непонятными запросами) такого ПРОСТОГО решения нет. Там есть всякие извращения. Но я уже давно не читаю Хабр - там очень мало адекватного материала). Но если вы пишете блокнот на sql, и используете только sql, то даное решение вам не поможет. Не забываем голосовать за ответ. У меня есть некоторые вопросы, но задавать я и уже не могу.

Комментариев нет:

Отправить комментарий