Предвид вашите спецификации (плюс допълнителна информация в коментарите),
- Имате колона с числови идентификатори (цели числа) само с няколко (или умерено малко) пропуски.
- Очевидно няма или има няколко операции за запис.
- Колоната ви за идентификация трябва да бъде индексирана! Първичният ключ служи добре.
Заявката по-долу не се нуждае от последователно сканиране на голямата таблица, а само от сканиране на индекс.
Първо, вземете прогнози за основната заявка:
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
с до голямата маса. Това трябва да стане много бързо с наличен индекс. -
Накрая отрежете излишъка
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
)
TABLE 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
LANGUAGE plpgsql VOLATILE ROWS 1000 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
)
TABLE random_pick
LIMIT _limit;
END
$func$;
Обадете се:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Можете дори да направите това общо да работи за всяка таблица:Вземете името на колоната PK и таблицата като полиморфен тип и използвайте EXECUTE
... Но това е извън обхвата на този въпрос. Вижте:
- Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки
Възможна алтернатива
АКО вашите изисквания позволяват еднакви набори за повтарящи се обаждания (и говорим за повтарящи се обаждания) бих помислил за материализиран изглед . Изпълнете горната заявка веднъж и запишете резултата в таблица. Потребителите получават квази случаен избор със скорост на светкавица. Обновявайте произволния си избор на интервали или събития по ваш избор.
Postgres 9.5 въвежда TABLESAMPLE SYSTEM (n)
Където n
е процент. Ръководството:
BERNOULLI
иSYSTEM
всеки метод за вземане на проби приема единичен аргумент, който е частта от таблицата към извадката, изразена катопроцент между 0 и 100 . Този аргумент може да бъде всекиreal
-ценен израз.
Удебелен акцент мой. Това е много бързо , но резултатът не е точно случаен . Отново ръководството:
SYSTEM
методът е значително по-бърз отBERNOULLI
метод, когато са посочени малки проценти на извадка, но може да върне безпроизволна извадка от таблицата в резултат на клъстерни ефекти.
Броят на върнатите редове може да варира значително. За нашия пример, за да получите приблизително 1000 реда:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Свързано:
- Бърз начин да откриете броя на редовете на таблица в PostgreSQL
Или инсталирайте допълнителния модул tsm_system_rows за да получите точно броя на исканите редове (ако има достатъчно) и да позволите по-удобния синтаксис:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Вижте отговора на Евън за подробности.
Но това все още не е съвсем случайно.