Простые случайные выборки из базы данных 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-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().

Это даже не работает на SQL-сервере, потому что RAND() возвращает одно и то же значение при каждом последующем вызове.

Andrew Mao 20.09.2012 20:43

Хороший замечание - я добавлю замечание, что пользователям SQL Server следует вместо этого использовать ORDER BY NEWID ().

ojrac 20.09.2012 23:14

Это все еще ужасно неэффективно, потому что ему нужно сортировать все данные. Техника случайной выборки для некоторого процента лучше, но я даже после прочтения кучи сообщений здесь не нашел приемлемого решения, которое было бы достаточно случайным.

Andrew Mao 21.09.2012 01:11

Если вы читаете вопрос, я спрашиваю конкретно, потому что ORDER BY RAND () - это O (n lg n).

ojrac 27.09.2012 06:25

Ответ muposat ниже великолепен, если вы не слишком одержимы статистической случайностью RAND ().

Josh Greifer 18.11.2014 13:14
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
101
5
134 739
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Может ты мог бы сделать

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)

Похоже, это выберет случайный фрагмент моих данных; Я ищу что-то посложнее - 10 000 случайно распределенных строк.

ojrac 30.10.2008 08:35

Тогда ваш единственный вариант, если вы хотите сделать это в базе данных, - это ORDER BY rand ().

staticsan 03.11.2008 03:29
Ответ принят как подходящий

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.

Мы можем попробовать три предположения:

  1. в таблице есть уникальный индексированный первичный ключ

  2. количество случайных строк, которые вы хотите выбрать (m), намного меньше, чем количество строк в таблице (n)

  3. уникальный первичный ключ - это целое число от 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) &lt 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, вероятно, будет лучше для требуемого типа цикла и генерации случайных чисел. .

Я бы рекомендовал добавить уникальный индекс для случайного выбора ключа и, возможно, игнорировать дубликаты во вставке, тогда вы можете избавиться от отдельных вещей, и соединение будет быстрее.

Sam Saffron 31.10.2008 10:08

Я думаю, что алгоритм случайных чисел может использовать некоторые настройки - либо ограничение UNIQUE, как упомянуто, либо просто сгенерировать числа 2 * m и SELECT DISTINCT, ORDER BY id (first-come-first-serve, поэтому это сводится к ограничению UNIQUE ) LIMIT m. Мне это нравится.

ojrac 31.10.2008 18:15

Что касается добавления уникального индекса к случайному выбору ключа, а затем игнорирования дубликатов при вставке, я подумал, что это может вернуть вас к поведению O (m ^ 2) вместо O (m lg m) для сортировки. Не уверен, насколько эффективно сервер поддерживает индекс при вставке случайных строк по одной.

user12861 31.10.2008 19:02

Что касается предложений по генерации чисел 2 * m или чего-то в этом роде, я хотел, чтобы алгоритм гарантированно работал, несмотря ни на что. Всегда есть (небольшая) вероятность, что ваши случайные числа размером 2 * m будут иметь более m дубликатов, поэтому вам не хватит для вашего запроса.

user12861 31.10.2008 19:05

Пока вы обращаете внимание на парадокс дня рождения, вы можете легко сгенерировать количество случайных чисел с астрономически низкой вероятностью <m уникальных значений. Но, в худшем случае, вы всегда можете сгенерировать еще m ключей, пока у вас не будет достаточно уникальных. ;)

ojrac 01.11.2008 20:10

Как я и предложил, поскольку вероятность дублирования в любом случае будет астрономически низкой, я просто создаю по одному за раз, если необходимо. Вряд ли нам понадобится еще один.

user12861 02.11.2008 05:25

Как узнать количество строк в таблице?

Awesome-o 24.02.2014 09:11

Просто используйте

WHERE RAND() < 0.1 

получить 10% записей или

WHERE RAND() < 0.01 

получить 1% записей и т. д.

Это вызовет RAND для каждой строки, что сделает его O (n). Плакат искал чего-то лучшего.

user12861 21.05.2012 19:23

Более того, RAND() возвращает одно и то же значение для последующих вызовов (по крайней мере, на MSSQL), что означает, что с такой вероятностью вы получите либо всю таблицу, либо ни одну из них.

Andrew Mao 20.09.2012 00:51

Я думаю, что самое быстрое решение - это

select * from table where rand() <= .3

Вот почему я думаю, что это должно сработать.

  • Он создаст случайное число для каждой строки. Число от 0 до 1.
  • Он определяет, отображать ли эту строку, если сгенерированное число находится в диапазоне от 0 до 0,3 (30%).

Это предполагает, что rand () генерирует числа с равномерным распределением. Это самый быстрый способ сделать это.

Я видел, что кто-то рекомендовал это решение, и они были сбиты без доказательств ... вот что я бы сказал на это -

  • Это O (n), но сортировка не требуется, поэтому она быстрее, чем O (n lg n)
  • mysql очень способен генерировать случайные числа для каждой строки. Попробуй это -

    выберите rand () из INFORMATION_SCHEMA.TABLES limit 10;

Поскольку рассматриваемая база данных - это mySQL, это правильное решение.

Во-первых, у вас есть проблема в том, что это на самом деле не отвечает на вопрос, поскольку возвращает полуслучайное количество результатов, близкое к желаемому, но не обязательно точно это число, вместо точного желаемого количества результатов.

user12861 07.02.2013 19:37

Далее, что касается эффективности, у вас O (n), где n - количество строк в таблице. Это не так хорошо, как O (m log m), где m - количество желаемых результатов, а m << n. Вы все равно можете быть правы, что на практике это будет быстрее, потому что, как вы говорите, генерация rand () и их сравнение с константой МОЖЕТ быть очень быстрой. Вам придется протестировать это, чтобы узнать. За меньшими столами вы можете выиграть. С огромными таблицами и гораздо меньшим количеством желаемых результатов я в этом сомневаюсь.

user12861 07.02.2013 19:40

Хотя @ user12861 прав в том, что не получает точного правильного числа, это хороший способ сократить набор данных до нужного приблизительного размера.

ojrac 08.02.2013 23:08

Как база данных обслуживает следующий запрос - SELECT * FROM table ORDER BY RAND() LIMIT 10000 ? Сначала он должен создать случайное число для каждой строки (как в описанном мной решении), а затем заказать его ... сортировка дорогая! Вот почему это решение БУДЕТ медленнее, чем описанное мною, поскольку сортировка не требуется. Вы можете добавить ограничение к описанному мною решению, и оно не даст вам больше, чем это количество строк. Как кто-то правильно заметил, он не даст вам ТОЧНОГО размера выборки, но со случайными выборками ТОЧНОСТЬ чаще всего не является строгим требованием.

ignorant 04.04.2013 01:28

Есть ли способ указать минимальное количество строк?

CMCDragonkai 16.03.2014 03:18

Проблема со случайностью в том, что это вероятность. Итак, если вам нужно 30% строк таблицы 100k, вы можете указать .3 в качестве случайного порога, а затем ограничить 30k, и это обычно сработает. Однако вы можете получить 25 тыс. Строк или 40 тыс. Строк в разных прогонах, поскольку это случайное распределение. Вы можете увеличить вероятность получения ровно 30 тыс. Строк, указав 0,4 в качестве случайного порога и ограничив 30 тыс., Но в конце вы можете увеличить только вероятность, а не абсолютные числа. Чем выше вы запрашиваете, тем больше у вас шансов получить минимальный набор строк, но это не совсем так.

ignorant 17.03.2014 21:29

Это предполагает, что rand() генерирует числа с униформа, а не нормальным распределением.

augurar 21.11.2014 02:37

Спасибо, что указали на это @augurar. Я обновил ответ. MYSQL не совсем единообразен, но «близок», см. это

ignorant 24.11.2014 19:11

Это не случайно. Он будет искусственно отдавать предпочтение строкам ранее в таблице, если вы укажете константу, которая даст вам необходимое количество строк.

symcbean 09.05.2016 23:43

Это неправильно ... если вы выберете каждую 5-ю строку из 100, вы получите 20 строк с разными временными шкалами ... будут ли они каждый раз одинаковыми 20 строками? зависит от базы данных, никаких гарантий по порядку строк в принципе не существует ... в любом случае, если вы заметили, в ответе нет LIMIT.

ignorant 10.05.2016 01:42

Начнем с наблюдения, что мы можем получить идентификаторы таблицы (например, 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, которая его поддерживает.

ojrac 01.05.2014 22:53

Я понимаю, что TABLESAMPLE не случайный в статистическом смысле.

Sean 04.05.2017 14:42

Хочу отметить, что все эти решения кажутся пробными без замены. Выбор верхних K строк из случайной сортировки или присоединение к таблице, содержащей уникальные ключи в случайном порядке, приведет к случайной выборке, сгенерированной без замены.

Если вы хотите, чтобы ваш образец был независимым, вам потребуется образец с заменой. См. В Вопрос 25451034 один из примеров того, как это сделать с помощью JOIN аналогично решению user12861. Решение написано для T-SQL, но концепция работает в любой базе данных SQL.

Быстрее, чем ORDER BY RAND ()

Я проверил, что этот метод намного быстрее, чем 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:

  1. Извлечь все значения ключевого столбца таблицы данных в любом порядке в массив на вашем любимом языке сценариев в O(n).
  2. Выполните Перемешивание Фишера-Йетса, остановившись после замены m, и извлеките подмассив [0:m-1] в ϴ(m).
  3. "Соедините" подмассив с исходным набором данных (например, 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.

ojrac 03.03.2020 17:28

Пытаться

SELECT TOP 10000 * FROM table ORDER BY NEWID()

Приведет ли это к желаемым результатам, не будучи слишком сложным?

Обратите внимание, что NEWID() специфичен для T-SQL.

Peter O. 15.10.2020 23:57

Мои извинения. Это. Спасибо. Тем не менее, полезно знать, приходит ли сюда кто-нибудь в лучшем виде, как я, и использует ли он T-SQL.

Northernlad 16.10.2020 17:36
ORDER BY NEWID() функционально аналогичен ORDER BY RAND() - он вызывает RAND() для каждой строки в наборе - O (n) - а затем сортирует все - O (n lg n). Другими словами, это наихудшее решение, которое нужно улучшить в этом вопросе.
ojrac 16.10.2020 21:04

В некоторых диалектах, таких как 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()). Есть некоторые ловушки (пример наиболее эффективных реализаций, основанный на структуре хранилища, которая может быть недостаточно случайной для некоторых приложений), но это отличный инструмент.

ojrac 18.12.2020 18:27

Другие вопросы по теме