Простые случайные выборки из базы данных Sql
Как взять эффективную простую случайную выборку в SQL? База данных, о которой идет речь, работает MySQL; моя таблица составляет по крайней мере 200 000 строк, и я хочу простую случайную выборку около 10 000.
"очевидный" ответ:
SELECT * FROM table ORDER BY RAND() LIMIT 10000
для больших таблиц это слишком медленно: он вызывает RAND () для каждой строки(которая уже ставит его в O(n)) и сортирует их, делая его O (N lg n) в лучшем случае. Есть ли способ сделать это быстрее, чем O(n)?
Примечание: As Эндрю Мао указывает в комментариях, если вы используете этот подход на SQL Server, вы должны использовать функцию T-SQL NEWID (), потому что RAND ()может возвращать одно и то же значение для всех строк.
EDIT: 5 ЛЕТ СПУСТЯ
Я снова столкнулся с этой проблемой с большей таблицей и в итоге использовал версию решения @ignorant с двумя настройками:
- пример строки для 2-5х нужный размер, недорого заказ RAND ()
- сохраните результат RAND () в индексированном столбце при каждой вставке/обновлении. (Если ваш набор данных не очень тяжелый для обновления, вам может потребоваться найти другой способ сохранить этот столбец свежим.)
чтобы взять 1000-элементный образец таблицы, я считаю строки и пробую результат в среднем до 10 000 строк со столбцом frozen_rand:
SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high
SELECT *
FROM table
WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000
(моя фактическая реализация включает в себя больше работы, чтобы убедиться, что я не undersample, и вручную обернуть rand_high вокруг, но основная идея заключается в том, "случайным образом сократить N до нескольких тысяч.")
хотя это приносит некоторые жертвы, это позволяет мне пробовать базу данных вниз с помощью сканирования индекса, пока она не будет достаточно мала, чтобы заказать RAND() снова.
9 ответов
здесь очень интересное обсуждение этого типа вопроса: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/
Я думаю, что абсолютно без предположений о таблице, что ваше решение O(n lg n) является лучшим. Хотя на самом деле с хорошим оптимизатором или немного другой техникой запрос, который вы перечисляете, может быть немного лучше, O (m*n), где m-количество случайных строк, так как это не necesssarily приходится сортировать весь большой массив, это может быть просто поиск наименьшего M раз. Но для тех номеров, которые вы разместили, m больше, чем lg n в любом случае.
три asumptions мы могли бы попробовать:
существует уникальный, индексированный, первичный ключ в таблице
количество случайных строк, которые вы хотите выбрать (m), намного меньше, чем количество строк в таблице (n)
уникальный первичный ключ-это целое число в диапазоне от 1 до n без пробелов
только с предположениями 1 и 2 я думаю, что это можно сделать в O(n), хотя вам нужно будет написать целый индекс в таблицу, чтобы соответствовать предположению 3, поэтому это не обязательно быстрый O(n). Если мы можем дополнительно предположить что-то еще хорошее в таблице, мы можем выполнить задачу в O(M log m). Предположение 3 было бы легким приятным дополнительным свойством для работы. С хорошим генератором случайных чисел, который гарантируется отсутствие дубликатов при генерации m чисел подряд, возможно решение O(m).
учитывая три предположения, основная идея состоит в том, чтобы создать m уникальных случайных чисел между 1 и n, а затем выбрать строки с этими ключами из таблицы. У меня сейчас нет mysql или чего-то еще передо мной, поэтому в слегка псевдокоде это будет выглядеть примерно так:
create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)
-- generate m random keys between 1 and n
for i = 1 to m
insert RandomKeysAttempt select rand()*n + 1
-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt
-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) < m
NextAttempt = rand()*n + 1
if not exists (select * from RandomKeys where RandomKey = NextAttempt)
insert RandomKeys select NextAttempt
-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey
Если вы действительно беспокоились об эффективности, вы могли бы рассмотреть возможность выполнения случайного генерация ключей на каком-то процедурном языке и вставка результатов в базу данных, поскольку почти все, что угодно, кроме SQL, вероятно, было бы лучше в виде цикла и генерации случайных чисел.
Я думаю, что самое быстрое решение -
select * from table where rand() <= .3
вот почему я думаю, что это должно сделать работу.
- он создаст случайное число для каждой строки. Число между 0 и 1
- он оценивает, следует ли отображать эту строку, если сгенерированное число от 0 до .3 (30%).
предполагается, что rand() генерирует числа в равномерном распределении. Это самый быстрый способ сделать это.
Я видел, что кто-то порекомендовал это решение, и они были сбиты без доказательств.. вот что я бы на это сказал ... --2-->
- Это O(n), но сортировка не требуется, поэтому она быстрее, чем O (N lg n)
-
mysql очень способен генерировать случайные числа для каждой строки. Попробуйте это -
выберите rand () из INFORMATION_SCHEMA.Ограничение таблиц 10;
поскольку рассматриваемая база данных-mySQL, это правильное решение.
быстрее, чем заказ по RAND ()
я протестировал этот метод, чтобы быть намного быстрее, чем ORDER BY RAND()
, следовательно, он работает в O (n) времени, и делает это впечатляюще быстро.
от http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx:
версия без MSSQL -- Я не проверял это
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()
версия MSSQL:
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
это позволит выбрать ~1% записей. Так что если вы нужно точное # процентов или записей, которые будут выбраны, оценить свой процент с некоторым запасом безопасности, а затем случайным образом вырвать лишние записи из результирующего набора, используя более дорогой ORDER BY RAND()
метод.
Еще Быстрее
я смог улучшить этот метод еще больше, потому что у меня был хорошо известный диапазон значений индексированных столбцов.
например, если у вас есть индексированный столбец с равномерно распределенные целые числа [0..max], вы можете использовать это для случайного выбора N небольшой интервал. Сделайте это динамически в своей программе, чтобы получить другой набор для каждого запуска запроса. Этот выбор подмножества будет O (N), который может на много порядков меньше, чем ваш полный набор данных.
в моем тесте я сократил время, необходимое для получения 20 (из 20 mil) образцов записей из 3 минуты использование ORDER BY RAND () до 0.0 секунд!
по-видимому, в некоторых версиях SQL есть TABLESAMPLE
command, но это не во всех реализациях SQL (в частности, Redshift).
http://technet.microsoft.com/en-us/library/ms189108 (v=sql.105).aspx
просто использовать
WHERE RAND() < 0.1
чтобы получить 10% записей или
WHERE RAND() < 0.01
чтобы получить 1% записей и т. д.
начиная с наблюдения, что мы можем получить идентификаторы из таблицы (например. графа 5) на основе набора:
select *
from table_name
where _id in (4, 1, 2, 5, 3)
мы можем прийти к результату, что если бы мы могли сгенерировать строку "(4, 1, 2, 5, 3)"
, тогда у нас был бы более эффективный способ, чем RAND()
.
например, в Java:
ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');
если идентификаторы имеют пробелы, то начальный arraylist indices
является результатом sql-запроса по идентификаторам.
Я хочу отметить, что все эти решения кажутся образца без замены. Выбор верхних K строк из случайной сортировки или присоединение к таблице, содержащей уникальные ключи в случайном порядке, даст случайную выборку, сгенерированную без замены.
Если вы хотите, чтобы ваш образец был независимым, вам нужно будет попробовать с заменой. См.вопрос 25451034 для одного примера того, как это сделать, используя соединение способом, подобным решению user12861. Этот решение написано для T-SQL, но концепция работает в любой SQL БД.
Если вам нужно точно m
строки, реально вы будете генерировать подмножество идентификаторов за пределами SQL. Большинство методов требуют в какой-то момент выбрать запись "nth", а таблицы SQL на самом деле не являются массивами вообще. Предположение, что ключи последовательны, чтобы просто присоединиться к случайным входам между 1 и количеством, также трудно удовлетворить - MySQL, например, не поддерживает его изначально, и условия блокировки... хитрый.
вот