Простые случайные выборки из базы данных Sql
Как я могу взять эффективную простую случайную выборку в SQL? База данных, о которой идет речь, работает под управлением MySQL; моя таблица составляет не менее 200 000 строк, и мне нужна простая случайная выборка около 10 000.
"очевидный" ответ:
SELECT * FROM table ORDER BY RAND() LIMIT 10000
для больших таблиц это слишком медленно: он вызывает RAND() для каждой строки (которая уже помещает ее в O(n)) и сортирует их, делая ее o(n lg n) в лучшем случае. Есть ли способ сделать это быстрее, чем O(n)?
Примечание: как Эндрю Мао указывает в комментариях, если вы используете этот подход на SQL Server, вы должны использовать функцию T-SQL NEWID (), потому что RAND ()может возвращать одно и то же значение для всех строк.
РЕДАКТИРОВАТЬ: 5 ЛЕТ СПУСТЯ
Я снова столкнулся с этой проблемой с большей таблицей и в итоге использовал версию решения @ignorant с двумя настройками:
- образец строк до 2-5x мой желаемый размер выборки, чтобы дешево заказать Рэнд ()
- сохранить результат 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
(моя фактическая реализация включает в себя больше работы, чтобы убедиться, что я не занижаю пример, и вручную обернуть 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-количество случайных строк, так как это не так необходимо отсортировать весь большой массив, он может просто искать наименьшие m раз. Но для тех номеров, которые вы опубликовали, m все равно больше, чем lg n.
три предположения, которые мы могли бы попробовать:
в таблице есть уникальный индексированный первичный ключ
количество случайных строк, которые вы хотите выбрать (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() генерирует числа в равномерном распределении. Это самый быстрый способ сделать это.
Я видел, что кто-то был рекомендовал это решение, и они были сбиты без доказательств.. вот что я бы сказал на это -
- Это 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 мил) образцов записей из 3 минуты используя ORDER BY RAND () вплоть до 0.0 секунд!
видимо, в некоторых версиях SQL есть
TABLESAMPLEкоманда, но это не во всех реализациях 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, например, не поддерживает его изначально, и условия блокировки... хитрый.вот
O(max(n, m lg n))времениO(n)-пространственного решения и при условии, просто используя кнопки:
- извлеките все значения ключевого столбца таблицы данных в любом порядке в массив на вашем любимом языке сценариев в
O(n)- выполнить Фишер-Йейтс перемешать остановка после
mсвопы, и извлечь подмассив[0:m-1]наϴ(m)- "присоединиться" подмассив с исходным набором данных (например,
SELECT ... WHERE id IN (<subarray>)) inO(m lg n)любой метод, который генерирует случайное подмножество вне SQL должен иметь по крайней мере эту сложность. Соединение не может быть быстрее, чем
O(m lg n)С BTREE (такO(m)претензии являются фантазией для большинства двигателей) и перетасовка ограничена нижеnиm lg nи не влияет на асимптотическое поведение.в обновления псевдокод:
ids = sql.query('SELECT id FROM t') for i in range(m): r = int(random() * (len(ids) - i)) ids[i], ids[i + r] = ids[i + r], ids[i] results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])