Лучший способ выбрать случайные строки PostgreSQL
Я хочу случайный выбор строк в PostgreSQL, я пробовал это:
select * from table where random() < 0.01;
но некоторые другие рекомендуют это:
select * from table order by random() limit 1000;
у меня очень большая таблица с 500 миллионами строк, я хочу, чтобы это было быстро.
какой подход лучше? В чем же разница? Каков наилучший способ выбора случайных строк?
11 ответов:
учитывая ваши спецификации (плюс дополнительная информация в комментариях),
- у вас есть числовой столбец ID (целые числа) с несколькими (или умеренно мало) пробелами.
- очевидно, нет или несколько операций записи.
- ваш столбец ID должен быть проиндексирован! Первичный ключ служит хорошо.
запрос ниже не требует последовательного сканирования большой таблицы, только сканирование индекса.
во-первых, получить оценки для основного запрос:
SELECT count(*) AS ct -- optional , min(id) AS min_id , max(id) AS max_id , max(id) - min(id) AS id_span FROM big;единственная, возможно, дорогая часть-это
count(*)(для больших таблиц). Учитывая вышеуказанные характеристики, вам это не нужно. Оценка будет делать просто отлично, доступны почти бесплатно (подробное объяснение здесь):SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;пока
ctне много меньше, чемid_span, запрос будет превосходить другие подходы.WITH params AS ( SELECT 1 AS min_id -- minimum id <= current min id , 5100000 AS id_span -- rounded up. (max_id - min_id + buffer) ) SELECT * FROM ( SELECT p.min_id + trunc(random() * p.id_span)::integer AS id FROM params p ,generate_series(1, 1100) g -- 1000 + buffer GROUP BY 1 -- trim duplicates ) r JOIN big USING (id) LIMIT 1000; -- trim surplus
генерировать случайные числа в
idпространство. У вас есть "пробелы", поэтому добавьте 10 % (достаточно, чтобы легко заполнить пустоту) количество строк для извлечения.каждого
idможет быть выбран несколько раз случайно (хотя очень маловероятно с большим пространством идентификатора), поэтому сгруппируйте сгенерированные числа (или используйтеDISTINCT).стать
ids к большому столу. Это должно быть очень быстро с индексом.наконец-то обрезать излишки
idчто не был съеден дураками и пробелами. Каждая строка имеет абсолютно равные шансы ковыряться.короткая версия
вы можете упростить этот запрос. Это выражение в приведенный выше запрос является только для образовательных целей:
SELECT * FROM ( SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id FROM generate_series(1, 1100) g ) r JOIN big USING (id) LIMIT 1000;уточните с помощью rCTE
особенно если вы не так уверены в пробелах и оценках.
WITH RECURSIVE random_pick AS ( SELECT * FROM ( SELECT 1 + trunc(random() * 5100000)::int AS id FROM generate_series(1, 1030) -- 1000 + few percent - adapt to your needs LIMIT 1030 -- hint for query planner ) r JOIN big b USING (id) -- eliminate miss UNION -- eliminate dupe SELECT b.* FROM ( SELECT 1 + trunc(random() * 5100000)::int AS id FROM random_pick r -- plus 3 percent - adapt to your needs LIMIT 999 -- less than 1000, hint for query planner ) r JOIN big b USING (id) -- eliminate miss ) SELECT * FROM random_pick LIMIT 1000; -- actual limitмы можем работать с меньше избыток в базовом запросе. Если слишком много пробелов, поэтому мы не находим достаточно строк в первой итерации, rCTE продолжает итерацию с рекурсивным членом. Нам еще нужно относительно несколько пробелы в пространстве идентификаторов или рекурсия может закончиться до достижения предела - или мы должны начать с достаточно большого буфера, который не соответствует цели оптимизации производительности.
дубликаты устраняются с помощью
UNIONв rCTE.внешний
LIMITостанавливает CTE, как только у нас будет достаточно строк.этот запрос тщательно составлен, чтобы использовать доступный индекс, генерировать фактически случайные строки и не останавливаться, пока мы не выполним предел (если рекурсия не закончится). Есть ряд подводных камней здесь, Если вы собираетесь ее переписать.
обернуть в функцию
для повторного использования с различными параметры:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03) RETURNS SETOF big AS $func$ DECLARE _surplus int := _limit * _gaps; _estimate int := ( -- get current estimate from system SELECT c.reltuples * _gaps FROM pg_class c WHERE c.oid = 'big'::regclass); BEGIN RETURN QUERY WITH RECURSIVE random_pick AS ( SELECT * FROM ( SELECT 1 + trunc(random() * _estimate)::int FROM generate_series(1, _surplus) g LIMIT _surplus -- hint for query planner ) r (id) JOIN big USING (id) -- eliminate misses UNION -- eliminate dupes SELECT * FROM ( SELECT 1 + trunc(random() * _estimate)::int FROM random_pick -- just to make it recursive LIMIT _limit -- hint for query planner ) r (id) JOIN big USING (id) -- eliminate misses ) SELECT * FROM random_pick LIMIT _limit; END $func$ LANGUAGE plpgsql VOLATILE ROWS 1000;звоните:
SELECT * FROM f_random_sample(); SELECT * FROM f_random_sample(500, 1.05);вы даже можете сделать это общим для работы с любой таблицей: возьмите имя столбца PK и таблицы как полиморфный тип и используйте
EXECUTE... Но это выходит за рамки данного вопроса. Смотрите:возможная альтернатива
если ваши требования позволяют идентичные наборы для повторных звонки (и мы говорим о повторных вызовах) я бы рассмотрел материализованного представления. Выполните вышеуказанный запрос один раз и запишите результат в таблицу. Пользователи получают квази случайный выбор со скоростью молнии. Обновите свой случайный выбор с интервалами или событиями по вашему выбору.
Postgres 9.5 вводит
TABLESAMPLE SYSTEM (n)это очень быстро, но результат не совсем случайный. инструкции:
The
SYSTEMметод значительно быстрее, чемBERNOULLIспособ при указанных малый процент отбора, но он может вернуть менее-случайная выборка таблицы в результате кластеризации эффектов.и количество возвращенных строк может сильно варьироваться. Для нашего примера, чтобы получить примерно 1000 строк, попробуйте:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);здесь n - это процент. Руководство:
The
BERNOULLIиSYSTEMметоды выборки каждый принимает один аргумент, который является частью таблицы для выборки, выраженной в виде процент от 0 до 100. Этот аргумент может быть любымrealзначением выражения.жирным выделено мной.
по теме:
или установите дополнительный модуль tsm_system_rows чтобы получить точное количество запрошенных строк (если их достаточно) и обеспечить более удобный синтаксис:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);посмотреть ответ Эвана для сведения.
но это все еще не совсем случайно.
вы можете изучить и сравнить план выполнения с помощью
EXPLAIN select * from table where random() < 0.01; EXPLAIN select * from table order by random() limit 1000;быстрый тест на большом столе1 показывает, что
ORDER BYсначала сортирует всю таблицу, а затем выбирает первые 1000 элементов. Сортировка большой таблицы не только считывает эту таблицу, но также включает чтение и запись временных файлов. Элементwhere random() < 0.1только сканирует всю таблицу один раз.для больших таблиц это может быть не то, что вы хотите, так как даже одно полное сканирование таблицы может это займет много времени.
третье предложение было бы
select * from table where random() < 0.01 limit 1000;Это один останавливает сканирование таблицы, как только 1000 строк были найдены и, следовательно, возвращается раньше. Конечно, это немного заболачивает случайность, но, возможно, это достаточно хорошо в вашем случае.
Edit: кроме этого соображения, вы можете проверить уже заданные вопросы для этого. Используя запрос
[postgresql] randomвозвращает довольно много просмотров.
- быстрая случайный выбор строк в Postgres
- как получить рандомизированные строки данных из таблицы postgreSQL?
- postgres: получить случайные записи из таблицы-слишком медленно
и связанная статья depez, описывающая еще несколько подходов:
1 "большой", как в"полная таблица не поместится в память".
postgresql order BY random (), выберите строки в случайном порядке:
select your_columns from your_table ORDER BY random()postgresql order BY random () with a distinct:
select * from (select distinct your_columns from your_table) table_alias ORDER BY random()порядок postgresql по случайному ограничению одной строки:
select your_columns from your_table ORDER BY random() limit 1
начиная с PostgreSQL 9.5, есть новый синтаксис, посвященный получению случайных элементов из таблицы:
SELECT * FROM mytable TABLESAMPLE SYSTEM (5);этот пример даст вам 5% элементов от
mytable.Смотрите больше объяснений на этом блоге:http://www.postgresql.org/docs/current/static/sql-select.html
тот, у кого есть ордер BY, будет медленнее.
select * from table where random() < 0.01;идет запись за записью, и решает случайным образом фильтровать его или нет. Это будетO(N)потому что ему нужно только проверить каждую запись один раз.
select * from table order by random() limit 1000;собирается отсортировать всю таблицу, а затем выбрать первую 1000. Помимо любой магии вуду за кулисами, порядок являетсяO(N * log N).обратная сторона
random() < 0.01заключается в том, что вы получите переменное число выходных учетная документация.
обратите внимание, что есть лучший способ перетасовки набора данных, чем сортировка случайным образом:Фишер-Йейтс Перемешать, который работает в
O(N). Однако реализация перетасовки в SQL звучит довольно сложно.
вот решение, которое работает для меня. Я думаю, что это очень просто понять и выполнить.
SELECT field_1, field_2, field_2, random() as ordering FROM big_table WHERE some_conditions ORDER BY ordering LIMIT 1000;
select * from table order by random() limit 1000;если вы знаете, сколько строк вы хотите, проверить
tsm_system_rows.tsm_system_rows
модуль предоставляет метод выборки таблиц SYSTEM_ROWS, который может быть использован в предложении TABLESAMPLE команды SELECT.
этот метод выборки таблиц принимает один целочисленный аргумент, который является максимальным числом строк для чтения. Полученный образец всегда будет содержать ровно столько строк, если только таблица не содержит достаточного количества строк, в этом случае выбирается вся таблица. как и встроенный метод выборки системы, SYSTEM_ROWS выполняет выборку на уровне блоков, так что выборка не является полностью случайной, но может подвергаться эффектам кластеризации, особенно если запрашивается только небольшое количество строк.
Сначала установите расширение
CREATE EXTENSION tsm_system_rows;затем ваш запрос,
SELECT * FROM table TABLESAMPLE SYSTEM_ROWS(1000);
Если вы хотите только одну строку, вы можете использовать вычисляемый
offsetполученные отcount.select * from table_name limit 1 offset floor(random() * (select count(*) from table_name));
вариация материализованного представления "возможная альтернатива" изложенные Эрвин Брандштеттер возможно.
скажем, например, что вы не хотите дубликатов в рандомизированных значениях, которые возвращаются. Таким образом, вам нужно будет установить логическое значение в первичной таблице, содержащей ваш (неслучайный) набор значений.
предполагая, что это входная таблица:
id_values id | used ----+-------- 1 | FALSE 2 | FALSE 3 | FALSE 4 | FALSE 5 | FALSE ...заполнить
ID_VALUESтаблицы по мере необходимости. Затем, как описано Эрвин, создайте материализованное представление, которое рандомизируетID_VALUESпосле:CREATE MATERIALIZED VIEW id_values_randomized AS SELECT id FROM id_values ORDER BY random();обратите внимание, что материализованное представление не содержит столбце, потому что это быстро станет устаревшим. Также представление не должно содержать другие столбцы, которые могут быть в
id_valuesтаблица.чтобы получить (и" потреблять") случайные значения, используйте UPDATE-RETURNING on
id_valuesвыборid_valuesСid_values_randomizedс соединением и применением необходимых критериев для получения только соответствующие возможности. Например:UPDATE id_values SET used = TRUE WHERE id_values.id IN (SELECT i.id FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id WHERE (NOT i.used) LIMIT 5) RETURNING id;изменить
LIMITпри необходимости -- если вам нужно только одно случайное значение за раз, изменитеLIMITдо1.с соответствующими индексами на
id_values, Я считаю, что обновление-возвращение должно выполняться очень быстро с небольшой нагрузкой. Он возвращает рандомизированные значения с одной базой данных туда и обратно. Критерии для" подходящих " строк могут быть настолько сложными, насколько это необходимо. Новые строки могут быть добавлены вid_valuesтаблица в любое время, и они станут доступными для приложения, как только материализованное представление будет обновлено (которое, вероятно, может быть запущено в нерабочее время). Создание и обновление материализованного представления будет медленным, но оно должно выполняться только при добавлении новых идентификаторов вid_valuesтаблица.
добавить столбец с именем
rС типомserial. Индексr.предположим, что у нас есть 200 000 строк, мы будем генерировать случайное число
n, где 0 nвыбрать строки с
r > nсортироватьASCи выберите самый маленький.код:
select * from YOUR_TABLE where r > ( select ( select reltuples::bigint AS estimate from pg_class where oid = 'public.YOUR_TABLE'::regclass) * random() ) order by r asc limit(1);код не требует пояснений. Подзапрос в середине используется для быстрой оценки количества строк таблицы из https://stackoverflow.com/a/7945274/1271094 .
на уровне приложения вам нужно выполнить инструкцию еще раз, если
n> количество строк или нужно выбрать несколько строк.
Я знаю, что немного опоздал на вечеринку, но я только что нашел этот удивительный инструмент под названием pg_sample:
pg_sample- извлечение небольшого образца набора данных из более крупной базы данных PostgreSQL при сохранении ссылочной целостности.Я пробовал это с базой данных 350M строк, и это было очень быстро, не знаю о случайность.
./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
Comments