Как быстро выбрать случайную строку из большой таблицы mysql?
Я работаю на php, но меня интересует любое решение, даже если оно на другом языке.
Как быстро выбрать случайную строку из большой таблицы mysql?
Я работаю на php, но меня интересует любое решение, даже если оно на другом языке.
Возьмите все идентификаторы, выберите из них случайный и получите полную строку.
Если вы знаете, что идентификаторы последовательны без дыр, вы можете просто взять максимум и вычислить случайный идентификатор.
Если здесь и там есть дыры, но в основном последовательные значения, и вас не волнует слегка искаженная случайность, возьмите максимальное значение, вычислите идентификатор и выберите первую строку с идентификатором, равным или превышающим тот, который вы вычислили. Причина перекоса в том, что идентификаторы, следующие за такими дырами, будут иметь больше шансов быть выбранными, чем те, которые следуют за другим идентификатором.
Если вы заказываете случайным образом, у вас будет ужасное сканирование таблицы, и слово быстро не применимо к такому решению.
Не делайте этого и не заказывайте по GUID, у него та же проблема.
Я знал, что должен быть способ сделать это в одном запросе быстрым способом. И вот оно:
Быстрый способ без привлечения внешнего кода, респект
http://jan.kneschke.de/projects/mysql/order-by-rand/
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
MediaWiki использует интересный прием (для функции Википедии Special:Random): в таблице со статьями есть дополнительный столбец со случайным числом (генерируется при создании статьи). Чтобы получить случайную статью, сгенерируйте случайное число и получите статью со следующим большим или меньшим (не помню каким) значением в столбце случайных чисел. С индексом это может быть очень быстро. (А MediaWiki написана на PHP и разработана для MySQL.)
Этот подход может вызвать проблему, если результирующие числа плохо распределены; IIRC, это было исправлено в MediaWiki, поэтому, если вы решите сделать это таким образом, вам следует взглянуть на код, чтобы увидеть, как это делается в настоящее время (вероятно, они периодически регенерируют столбец случайных чисел).
SELECT id FROM table WHERE randNumb >= RAND() ORDER BY randNumb LIMIT 5
будет ускорено индексирование. Если вы используете MyISAM, вам может понадобиться составной индекс для (randNumb, id)
.
- person O. Jones; 05.05.2017
Вот решение, которое работает довольно быстро и обеспечивает лучшее случайное распределение, не зависящее от того, являются ли значения id непрерывными или начинаются с 1.
SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM mytable)));
SET @sql := CONCAT('SELECT * FROM mytable LIMIT ', @r, ', 1');
PREPARE stmt1 FROM @sql;
EXECUTE stmt1;
$query
равным приведенному выше, а затем выполнение обычного mysql_query($query)
не возвращает никаких результатов. Спасибо.
- person ProgrammerGirl; 12.04.2012
COUNT(*)
(при условии InnoDB), что меньше, чем полное сканирование для OFFSET @r
. Но он превосходен в том, что он случайный и не зависит от свойств идентификатора.
- person Rick James; 07.08.2015
Возможно, вы могли бы сделать что-то вроде:
SELECT * FROM table
WHERE id=
(FLOOR(RAND() *
(SELECT COUNT(*) FROM table)
)
);
Это предполагает, что все ваши идентификационные номера являются последовательными без пробелов.
Добавьте столбец, содержащий вычисленное случайное значение, в каждую строку и используйте его в предложении упорядочения, ограничившись одним результатом при выборе. Это работает быстрее, чем сканирование таблицы, которое вызывает ORDER BY RANDOM()
.
Обновление: вам по-прежнему необходимо вычислить некоторое случайное значение перед выдачей оператора SELECT
при извлечении, конечно, например.
SELECT * FROM `foo` WHERE `foo_rand` >= {some random value} LIMIT 1
foo
WHERE foo_rand
›= {некоторое случайное значение} ORDER BY foo_rand LIMIT 1?
- person haibuihoang; 21.04.2014
Есть еще один способ создать случайные строки, используя только запрос и без порядка с помощью rand(). Он включает в себя пользовательские переменные. См. как создавать случайные строки из таблицы а>
Чтобы найти случайные строки из таблицы, не используйте ORDER BY RAND(), потому что это заставляет MySQL выполнять полную сортировку файлов и только затем извлекать требуемое предельное количество строк. Чтобы избежать полной сортировки файлов, используйте функцию RAND() только в предложении where. Он остановится, как только достигнет необходимого количества строк. См. http://www.rndblog.com/how-to-select-random-rows-in-mysql/
если вы не удаляете строку в этой таблице, наиболее эффективным способом является:
(если вы знаете идентификатор mininum, просто пропустите его)
SELECT MIN(id) AS minId, MAX(id) AS maxId FROM table WHERE 1
$randId=mt_rand((int)$row['minId'], (int)$row['maxId']);
SELECT id,name,... FROM table WHERE id=$randId LIMIT 1
Я вижу здесь много решений. Одно или два кажутся нормальными, но другие решения имеют некоторые ограничения. Но следующее решение будет работать для всех ситуаций
select a.* from random_data a, (select max(id)*rand() randid from random_data) b
where a.id >= b.randid limit 1;
Здесь, id, не нужно быть последовательным. Это может быть любой первичный ключ/уникальный/автоинкрементный столбец. См. следующее Самый быстрый способ выбрать случайную строку из большой таблицы MySQL
Спасибо Zillur - www.techinfobest.com
Для выбора нескольких случайных строк из заданной таблицы (скажем, «слов») наша команда придумала вот такую красоту:
SELECT * FROM
`words` AS r1 JOIN
(SELECT MAX(`WordID`) as wid_c FROM `words`) as tmp1
WHERE r1.WordID >= (SELECT (RAND() * tmp1.wid_c) AS id) LIMIT n
Классический «SELECT id FROM table ORDER BY RAND() LIMIT 1» на самом деле в порядке.
См. следующий отрывок из руководства по MySQL:
Если вы используете LIMIT row_count с ORDER BY, MySQL завершит сортировку, как только найдет первые строки row_count отсортированного результата, а не сортирует весь результат.
При заказе вы сделаете полный скан таблицы. Лучше всего, если вы сделаете выбор счетчика (*), а затем получите случайную строку = rownum между 0 и последним реестром.
Простым, но медленным способом будет (хорошо для небольших таблиц)
SELECT * from TABLE order by RAND() LIMIT 1
В псевдокоде:
sql "select id from table"
store result in list
n = random(size of list)
sql "select * from table where id=" + list[n]
Это предполагает, что id
является уникальным (первичным) ключом.
Взгляните на эту ссылку Яна Кнешке или этот ответ SO, поскольку они оба обсуждают тот же вопрос. В ответе SO также рассматриваются различные варианты и есть несколько хороших предложений в зависимости от ваших потребностей. Ян рассматривает все различные варианты и рабочие характеристики каждого из них. Он заканчивает следующим образом для наиболее оптимизированного метода, с помощью которого можно сделать это в MySQL select:
SELECT name
FROM random AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id)
FROM random)) AS id)
AS r2
WHERE r1.id >= r2.id
ORDER BY r1.id ASC
LIMIT 1;
ХТХ,
-Дипин
Я немного новичок в SQL, но как насчет генерации случайного числа в PHP и использования
SELECT * FROM the_table WHERE primary_key >= $randNr
это не решает проблему с дырками в таблице.
Но вот поворот предложения lassevks:
SELECT primary_key FROM the_table
Используйте mysql_num_rows() в PHP для создания случайного числа на основе приведенного выше результата:
SELECT * FROM the_table WHERE primary_key = rand_number
Кстати, обратите внимание, насколько медленным является SELECT * FROM the_table
:
создание случайного числа на основе mysql_num_rows()
и последующее перемещение указателя данных в эту точку mysql_data_seek()
. Насколько медленным это будет для больших таблиц, скажем, с миллионом строк?
Я столкнулся с проблемой, когда мои идентификаторы не были последовательными. Что это я придумал.
SELECT * FROM products WHERE RAND()<=(5/(SELECT COUNT(*) FROM products)) LIMIT 1
Возвращаемых строк примерно 5, но я ограничиваю их до 1.
Если вы хотите добавить еще одно предложение WHERE, это становится немного интереснее. Допустим, вы хотите найти товары со скидкой.
SELECT * FROM products WHERE RAND()<=(100/(SELECT COUNT(*) FROM pt_products)) AND discount<.2 LIMIT 1
Что вам нужно сделать, так это убедиться, что вы возвращаете достаточно результатов, поэтому я установил его на 100. Наличие предложения WHERE Discount‹.2 в подзапросе было в 10 раз медленнее, поэтому лучше возвращать больше результатов и ограничение.
Используйте приведенный ниже запрос, чтобы получить случайную строку
SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails
GROUP BY usr_fk_id
ORDER BY cnt ASC
LIMIT 1
В моем случае моя таблица имеет идентификатор в качестве первичного ключа, автоинкремент без пробелов, поэтому я могу использовать COUNT(*)
или MAX(id)
для получения количества строк.
Я сделал этот скрипт для проверки самой быстрой работы:
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
Результаты:
36.8418693542479 ms
0.241041183472 ms
0.216960906982 ms
Ответ с методом заказа:
SELECT FLOOR(RAND() * (
SELECT id FROM tbl ORDER BY id DESC LIMIT 1
)) n FROM tbl LIMIT 1
...
SELECT * FROM tbl WHERE id = $result;
Я использовал это, и задание было выполнено по ссылке из здесь
SELECT * FROM myTable WHERE RAND()<(SELECT ((30/COUNT(*))*10) FROM myTable) ORDER BY RAND() LIMIT 30;
Создайте функцию, чтобы сделать это, скорее всего, лучший ответ и самый быстрый ответ здесь!
Плюсы - Работает даже с гапсами и очень быстро.
<?
$sqlConnect = mysqli_connect('localhost','username','password','database');
function rando($data,$find,$max = '0'){
global $sqlConnect; // Set as mysqli connection variable, fetches variable outside of function set as GLOBAL
if($data == 's1'){
$query = mysqli_query($sqlConnect, "SELECT * FROM `yourtable` ORDER BY `id` DESC LIMIT {$find},1");
$fetched_data = mysqli_fetch_assoc($query);
if(mysqli_num_rows($fetched_data>0){
return $fetch_$data;
}else{
rando('','',$max); // Start Over the results returned nothing
}
}else{
if($max != '0'){
$irand = rand(0,$max);
rando('s1',$irand,$max); // Start rando with new random ID to fetch
}else{
$query = mysqli_query($sqlConnect, "SELECT `id` FROM `yourtable` ORDER BY `id` DESC LIMIT 0,1");
$fetched_data = mysqli_fetch_assoc($query);
$max = $fetched_data['id'];
$irand = rand(1,$max);
rando('s1',$irand,$max); // Runs rando against the random ID we have selected if data exist will return
}
}
}
$your_data = rando(); // Returns listing data for a random entry as a ASSOC ARRAY
?>
Пожалуйста, имейте в виду, что этот код не тестировался, но он является рабочей концепцией для возврата случайных записей даже с пробелами. Пока пробелы не достаточно велики, чтобы вызвать проблемы со временем загрузки.
SET @COUNTER=SELECT COUNT(*) FROM your_table;
SELECT PrimaryKey
FROM your_table
LIMIT 1 OFFSET (RAND() * @COUNTER);
Сложность первого запроса составляет O(1) для таблиц MyISAM.
Второй запрос сопровождает полное сканирование таблицы. Сложность = О (n)
Только для этой цели заведите отдельную таблицу. Вы также должны вставлять одни и те же строки в эту таблицу при каждой вставке в исходную таблицу. Предположение: нет DELETE.
CREATE TABLE Aux(
MyPK INT AUTO_INCREMENT,
PrimaryKey INT
);
SET @MaxPK = (SELECT MAX(MyPK) FROM Aux);
SET @RandPK = CAST(RANDOM() * @MaxPK, INT)
SET @PrimaryKey = (SELECT PrimaryKey FROM Aux WHERE MyPK = @RandPK);
Если DELETE разрешены,
SET @delta = CAST(@RandPK/10, INT);
SET @PrimaryKey = (SELECT PrimaryKey
FROM Aux
WHERE MyPK BETWEEN @RandPK - @delta AND @RandPK + @delta
LIMIT 1);
Общая сложность O(1).