Как можно организовать случайный выбор записей из таблицы, посредством одного запроса?
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, то даное решение вам не поможет.
Не забываем голосовать за ответ. У меня есть некоторые вопросы, но задавать я и
уже не могу.
Комментариев нет:
Отправить комментарий