Как мне взять эффективную простую случайную выборку в 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-5 раз больше желаемого размера выборки, чтобы
ORDER BY 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
(Моя реальная реализация требует дополнительной работы, чтобы убедиться, что я не недооцениваю выборку, и вручную обернуть rand_high, но основная идея - «случайным образом сократить число N до нескольких тысяч».)
Хотя это приносит некоторые жертвы, это позволяет мне выполнять выборку базы данных с помощью сканирования индекса, пока она снова не станет достаточно маленькой ORDER BY RAND()
.
источник
RAND()
что каждый последующий вызов возвращает одно и то же значение.Ответы:
Здесь есть очень интересное обсуждение этого типа проблем: 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
Вот почему я думаю, что это должно сработать.
Это предполагает, что rand () генерирует числа с равномерным распределением. Это самый быстрый способ сделать это.
Я видел, что кто-то рекомендовал это решение, и они были сбиты без доказательств ... вот что я могу сказать по этому поводу -
mysql очень способен генерировать случайные числа для каждой строки. Попробуй это -
выберите rand () из INFORMATION_SCHEMA.TABLES limit 10;
Поскольку рассматриваемая база данных - это mySQL, это правильное решение.
источник
SELECT * FROM table ORDER BY RAND() LIMIT 10000
? Сначала он должен создать случайное число для каждой строки (как в описанном мной решении), а затем заказать его ... сортировка дорогая! Вот почему это решение БУДЕТ медленнее, чем описанное мною, поскольку сортировка не требуется. Вы можете добавить ограничение к описанному мной решению, и оно не даст вам больше, чем это количество строк. Как кто-то правильно заметил, он не даст вам ТОЧНОГО размера выборки, но со случайными выборками ТОЧНОСТЬ чаще всего не является строгим требованием.Очевидно, в некоторых версиях SQL есть
TABLESAMPLE
команда, но она не во всех реализациях SQL (в частности, Redshift).http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx
источник
TABLESAMPLE
это не случайность в статистическом смысле.Просто используйте
получить 10% записей или
получить 1% записей и т. д.
источник
RAND()
возвращает одно и то же значение для последующих вызовов (по крайней мере, на MSSQL), что означает, что с такой вероятностью вы получите либо всю таблицу, либо ни одну из них.Быстрее, чем ORDER BY 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 секунды !
источник
Хочу отметить, что все эти решения кажутся пробными без замены. Выбор верхних K строк из случайной сортировки или присоединение к таблице, которая содержит уникальные ключи в случайном порядке, приведет к случайной выборке, сгенерированной без замены.
Если вы хотите, чтобы ваш образец был независимым, вам потребуется образец с заменой. См. Вопрос 25451034, где показан один из примеров того, как это сделать с помощью JOIN аналогично решению user12861. Решение написано для T-SQL, но концепция работает в любой базе данных SQL.
источник
Начнем с наблюдения, что мы можем получить идентификаторы таблицы (например, count 5) на основе набора:
select * from table_name where _id in (4, 1, 2, 5, 3)
мы можем прийти к выводу, что если бы мы могли сгенерировать строку
"(4, 1, 2, 5, 3)"
, то у нас был бы более эффективный способ, чемRAND()
.Например, в Java:
Если в идентификаторах есть пробелы, то начальный список массивов
indices
является результатом запроса sql для идентификаторов.источник
Если вам нужны ровно
m
строки, реально вы сгенерируете свое подмножество идентификаторов вне SQL. Большинству методов в какой-то момент требуется выбрать «n-ую» запись, а таблицы SQL на самом деле вовсе не массивы. Предположение о том, что ключи являются последовательными, чтобы просто объединить случайные целые числа между 1 и счетчиком, также трудно удовлетворить - например, MySQL не поддерживает его изначально, а условия блокировки ... сложные .Вот решение
O(max(n, m lg n))
-time, -space,O(n)
предполагающее только простые ключи BTREE:O(n)
m
и извлеките подмассив[0:m-1]
вϴ(m)
SELECT ... WHERE id IN (<subarray>)
) вO(m lg n)
Любой метод, который генерирует случайное подмножество вне SQL, должен иметь как минимум эту сложность. Соединение не может быть быстрее, чем
O(m lg n)
с BTREE (так чтоO(m)
утверждения являются фантастикой для большинства движков), а перемешивание ограничено снизуn
иm lg n
не влияет на асимптотическое поведение.В псевдокоде Pythonic:
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])
источник
Выберите 3000 случайных записей в Netezza:
WITH IDS AS ( SELECT ID FROM MYTABLE; ) SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000
источник
Пытаться
SELECT TOP 10000 * FROM table ORDER BY NEWID()
Дало бы это желаемые результаты, не будучи слишком сложным?
источник
NEWID()
это характерно для T-SQL.ORDER BY NEWID()
Функционально такой же, какORDER BY RAND()
- он вызываетRAND()
каждую строку в наборе - O (n) - а затем сортирует все - O (n lg n). Другими словами, это наихудший вариант решения, которое этот вопрос пытается улучшить.В некоторых диалектах, таких как Microsoft SQL Server, PostgreSQL и Oracle (но не в MySQL или SQLite), вы можете сделать что-то вроде
select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);
Причина, по которой нельзя просто
(10000 rows)
обойтись без него,top
заключается в том, чтоTABLESAMPLE
логика дает вам крайне неточное количество строк (например, иногда 75% больше, иногда 1,25% больше), поэтому вы хотите увеличить выборку и выбрать точное количество, которое хотите. ПредназначенREPEATABLE (123)
для предоставления случайного начального числа.источник
Может ты мог бы сделать
SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)
источник