Как мне взять эффективную простую случайную выборку в 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 с двумя настройками:
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().
Хороший замечание - я добавлю замечание, что пользователям SQL Server следует вместо этого использовать ORDER BY NEWID ().
Это все еще ужасно неэффективно, потому что ему нужно сортировать все данные. Техника случайной выборки для некоторого процента лучше, но я даже после прочтения кучи сообщений здесь не нашел приемлемого решения, которое было бы достаточно случайным.
Если вы читаете вопрос, я спрашиваю конкретно, потому что ORDER BY RAND () - это O (n lg n).
Ответ muposat ниже великолепен, если вы не слишком одержимы статистической случайностью RAND ().






Может ты мог бы сделать
SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)
Похоже, это выберет случайный фрагмент моих данных; Я ищу что-то посложнее - 10 000 случайно распределенных строк.
Тогда ваш единственный вариант, если вы хотите сделать это в базе данных, - это ORDER BY rand ().
There's a very interesting discussion of this type of issue here: 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, вероятно, будет лучше для требуемого типа цикла и генерации случайных чисел. .
Я бы рекомендовал добавить уникальный индекс для случайного выбора ключа и, возможно, игнорировать дубликаты во вставке, тогда вы можете избавиться от отдельных вещей, и соединение будет быстрее.
Я думаю, что алгоритм случайных чисел может использовать некоторые настройки - либо ограничение UNIQUE, как упомянуто, либо просто сгенерировать числа 2 * m и SELECT DISTINCT, ORDER BY id (first-come-first-serve, поэтому это сводится к ограничению UNIQUE ) LIMIT m. Мне это нравится.
Что касается добавления уникального индекса к случайному выбору ключа, а затем игнорирования дубликатов при вставке, я подумал, что это может вернуть вас к поведению O (m ^ 2) вместо O (m lg m) для сортировки. Не уверен, насколько эффективно сервер поддерживает индекс при вставке случайных строк по одной.
Что касается предложений по генерации чисел 2 * m или чего-то в этом роде, я хотел, чтобы алгоритм гарантированно работал, несмотря ни на что. Всегда есть (небольшая) вероятность, что ваши случайные числа размером 2 * m будут иметь более m дубликатов, поэтому вам не хватит для вашего запроса.
Пока вы обращаете внимание на парадокс дня рождения, вы можете легко сгенерировать количество случайных чисел с астрономически низкой вероятностью <m уникальных значений. Но, в худшем случае, вы всегда можете сгенерировать еще m ключей, пока у вас не будет достаточно уникальных. ;)
Как я и предложил, поскольку вероятность дублирования в любом случае будет астрономически низкой, я просто создаю по одному за раз, если необходимо. Вряд ли нам понадобится еще один.
Как узнать количество строк в таблице?
Просто используйте
WHERE RAND() < 0.1
получить 10% записей или
WHERE RAND() < 0.01
получить 1% записей и т. д.
Это вызовет RAND для каждой строки, что сделает его O (n). Плакат искал чего-то лучшего.
Более того, RAND() возвращает одно и то же значение для последующих вызовов (по крайней мере, на MSSQL), что означает, что с такой вероятностью вы получите либо всю таблицу, либо ни одну из них.
Я думаю, что самое быстрое решение - это
select * from table where rand() <= .3
Вот почему я думаю, что это должно сработать.
Это предполагает, что rand () генерирует числа с равномерным распределением. Это самый быстрый способ сделать это.
Я видел, что кто-то рекомендовал это решение, и они были сбиты без доказательств ... вот что я бы сказал на это -
mysql очень способен генерировать случайные числа для каждой строки. Попробуй это -
выберите rand () из INFORMATION_SCHEMA.TABLES limit 10;
Поскольку рассматриваемая база данных - это mySQL, это правильное решение.
Во-первых, у вас есть проблема в том, что это на самом деле не отвечает на вопрос, поскольку возвращает полуслучайное количество результатов, близкое к желаемому, но не обязательно точно это число, вместо точного желаемого количества результатов.
Далее, что касается эффективности, у вас O (n), где n - количество строк в таблице. Это не так хорошо, как O (m log m), где m - количество желаемых результатов, а m << n. Вы все равно можете быть правы, что на практике это будет быстрее, потому что, как вы говорите, генерация rand () и их сравнение с константой МОЖЕТ быть очень быстрой. Вам придется протестировать это, чтобы узнать. За меньшими столами вы можете выиграть. С огромными таблицами и гораздо меньшим количеством желаемых результатов я в этом сомневаюсь.
Хотя @ user12861 прав в том, что не получает точного правильного числа, это хороший способ сократить набор данных до нужного приблизительного размера.
Как база данных обслуживает следующий запрос - SELECT * FROM table ORDER BY RAND() LIMIT 10000 ? Сначала он должен создать случайное число для каждой строки (как в описанном мной решении), а затем заказать его ... сортировка дорогая! Вот почему это решение БУДЕТ медленнее, чем описанное мною, поскольку сортировка не требуется. Вы можете добавить ограничение к описанному мною решению, и оно не даст вам больше, чем это количество строк. Как кто-то правильно заметил, он не даст вам ТОЧНОГО размера выборки, но со случайными выборками ТОЧНОСТЬ чаще всего не является строгим требованием.
Есть ли способ указать минимальное количество строк?
Проблема со случайностью в том, что это вероятность. Итак, если вам нужно 30% строк таблицы 100k, вы можете указать .3 в качестве случайного порога, а затем ограничить 30k, и это обычно сработает. Однако вы можете получить 25 тыс. Строк или 40 тыс. Строк в разных прогонах, поскольку это случайное распределение. Вы можете увеличить вероятность получения ровно 30 тыс. Строк, указав 0,4 в качестве случайного порога и ограничив 30 тыс., Но в конце вы можете увеличить только вероятность, а не абсолютные числа. Чем выше вы запрашиваете, тем больше у вас шансов получить минимальный набор строк, но это не совсем так.
Это предполагает, что rand() генерирует числа с униформа, а не нормальным распределением.
Спасибо, что указали на это @augurar. Я обновил ответ. MYSQL не совсем единообразен, но «близок», см. это
Это не случайно. Он будет искусственно отдавать предпочтение строкам ранее в таблице, если вы укажете константу, которая даст вам необходимое количество строк.
Это неправильно ... если вы выберете каждую 5-ю строку из 100, вы получите 20 строк с разными временными шкалами ... будут ли они каждый раз одинаковыми 20 строками? зависит от базы данных, никаких гарантий по порядку строк в принципе не существует ... в любом случае, если вы заметили, в ответе нет LIMIT.
Начнем с наблюдения, что мы можем получить идентификаторы таблицы (например, count 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(']', ')');
Если в идентификаторах есть пробелы, то исходный массив массивов indices является результатом запроса sql по идентификаторам.
Очевидно, в некоторых версиях SQL есть команда TABLESAMPLE, но она не во всех реализациях SQL (особенно в Redshift).
http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx
Очень круто! Похоже, что это не реализовано ни в PostgreSQL, ни в MySQL / MariaDB, но это отличный ответ, если вы используете реализацию SQL, которая его поддерживает.
Я понимаю, что TABLESAMPLE не случайный в статистическом смысле.
Хочу отметить, что все эти решения кажутся пробными без замены. Выбор верхних K строк из случайной сортировки или присоединение к таблице, содержащей уникальные ключи в случайном порядке, приведет к случайной выборке, сгенерированной без замены.
Если вы хотите, чтобы ваш образец был независимым, вам потребуется образец с заменой. См. В Вопрос 25451034 один из примеров того, как это сделать с помощью JOIN аналогично решению user12861. Решение написано для T-SQL, но концепция работает в любой базе данных SQL.
Я проверил, что этот метод намного быстрее, чем ORDER BY RAND(), поэтому он работает за время На), и делает это впечатляюще быстро.
От 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 небольших интервалов. Сделайте это динамически в своей программе, чтобы получить разные наборы для каждого запуска запроса. Этот выбор подмножества будет НА), который может на много порядков меньше, чем ваш полный набор данных.
В моем тесте я сократил время, необходимое для получения 20 (из 20 миллионов) образцов записей из 3 мин., используя ORDER BY RAND (), до 0,0 секунды!
Если вам нужны именно строки m, реально вы сгенерируете свое подмножество идентификаторов вне SQL. Большинство методов требуют в какой-то момент выбрать «n-ую» запись, а таблицы SQL на самом деле вовсе не массивы. Предположение о том, что ключи являются последовательными, чтобы просто объединить случайные целые числа между 1 и счетчиком, также трудно удовлетворить - например, MySQL не поддерживает его изначально, и условия блокировки ... сложный.
Вот решение для времени O(max(n, m lg n)) и пространства 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
Я не думаю, что это ответ на вопрос о том, как запросить случайную выборку строк без ORDER BY rand () LIMIT $ 1, кроме добавления некоторых примечаний, связанных с диалектом SQL.
Пытаться
SELECT TOP 10000 * FROM table ORDER BY NEWID()
Приведет ли это к желаемым результатам, не будучи слишком сложным?
Обратите внимание, что NEWID() специфичен для T-SQL.
Мои извинения. Это. Спасибо. Тем не менее, полезно знать, приходит ли сюда кто-нибудь в лучшем виде, как я, и использует ли он 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) предназначен для предоставления случайного начального числа.
Это похоже на потенциально эффективную версию главного ответа (фильтрация с помощью RAND()). Есть некоторые ловушки (пример наиболее эффективных реализаций, основанный на структуре хранилища, которая может быть недостаточно случайной для некоторых приложений), но это отличный инструмент.
Это даже не работает на SQL-сервере, потому что
RAND()возвращает одно и то же значение при каждом последующем вызове.