С учетом ваших спецификаций (плюс дополнительная информация в комментариях),
- У вас есть столбец числового идентификатора (целые числа) только с небольшим (или умеренно небольшим) пробелом.
- Очевидно, нет или мало операций записи.
- Ваш столбец идентификатора должен быть проиндексирован!Первичный ключ отлично работает.
В приведенном ниже запросе не требуется последовательное сканирование большой таблицы, только сканирование индекса.
Сначала получите оценки для основного запроса:
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
).
Присоедините id
s к большой таблице.Это должно быть очень быстро при установленном индексе.
Наконец обрежьте излишки id
s, которые не были использованы дупсами и пробелами.Каждая строка имеет абсолютно равный шанс для выбора.
Короткая версия
Вы можете упростить этот запрос.CTE в приведенном выше запросе только для образовательных целей:
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
... Но это выходит за рамки этого вопроса.См .:
Возможная альтернатива
ЕСЛИ ваштребования допускают идентичные наборы для повторных вызовов (а мы говорим о повторных вызовах). Я бы рассмотрел материализованное представление .Выполните вышеуказанный запрос один раз и запишите результат в таблицу.Пользователи получают квази-случайный выбор со скоростью молнии.Обновите ваш случайный выбор через интервалы или события по вашему выбору.
Где n
в процентах. Руководство:
Каждый из методов выборки BERNOULLI
и SYSTEM
принимает один аргумент, представляющий собой долю таблицы в выборке, выраженную в процентах от 0 до 100 .Этот аргумент может быть любым real
-значным выражением.
Жирный акцент мой.Это очень быстро , но результат не совсем случайный .Руководство снова:
Метод SYSTEM
значительно быстрее, чем метод BERNOULLI
если указан небольшой процент выборки, но он может вернуть
Менее случайная выборка таблицы в результате эффектов кластеризации.
Количество возвращаемых строк может сильно отличаться. Для нашего примера, чтобы получить примерно 1000 строк:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Связанный:
Или установите дополнительный модуль tsm_system_rows , чтобы получить точное количество запрошенных строк (если их достаточно) и учесть более удобный синтаксис:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Подробнее см. Ответ Эвана .
Но это все еще не совсем случайно.