З огляду на ваші технічні характеристики (плюс додаткову інформацію в коментарях),
- У вас є стовпчик числового ідентифікатора (цілі числа) з лише невеликими (або помірно мало) проміжками.
- Очевидно, що немає або мало записують операцій.
- Свій стовпець ідентифікатора має бути проіндексовано! Первинний ключ добре працює.
Запит нижче не потребує послідовного сканування великої таблиці, лише індексного сканування.
Спочатку отримайте оцінки основного запиту:
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
, які не були з'їдені гномами та прогалинами. Кожен ряд має абсолютно рівний шанс бути вибраним.
Коротка версія
Ви можете спростити цей запит. 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);
Ви навіть можете зробити це загальним для роботи для будь-якої таблиці: Візьміть назву стовпця ПК та таблиці як поліморфний тип та використовуйте 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);
Див відповідь Евана для деталей.
Але це все ще не зовсім випадково.