Vzhledem k vašim specifikacím (plus další informace v komentářích)
- Máte číselný sloupec ID (celá čísla) s pouze několika (nebo středně málo) mezerami.
- Zřejmě žádné nebo jen málo operací zápisu.
- Váš sloupec ID musí být indexován! Primární klíč slouží dobře.
Níže uvedený dotaz nevyžaduje sekvenční skenování velké tabulky, pouze indexové skenování.
Nejprve získejte odhady pro hlavní dotaz:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Jedinou možná nákladnou částí je count(*)
(pro velké stoly). Vzhledem k výše uvedeným specifikacím to nepotřebujete. Odhad bude fungovat dobře, je k dispozici téměř bez nákladů (podrobné vysvětlení zde):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Dokud ct
není moc menší než id_span
, dotaz předčí ostatní přístupy.
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
-
Generujte náhodná čísla v
id
prostor. Máte "málo mezer", takže přidejte 10 % (dost na snadné zakrytí prázdných míst) k počtu řádků, které se mají načíst. -
Každé
id
lze náhodně vybrat vícekrát (i když je to velmi nepravděpodobné s velkou mezerou ID), proto seskupte vygenerovaná čísla (nebo použijteDISTINCT
). -
Připojte se k
id
k velkému stolu. To by mělo být velmi rychlé se zavedeným indexem. -
Nakonec ořízněte přebytek
id
s, které nebyly sežrány podvodníky a mezerami. Každý řádek má zcela stejnou šanci k výběru.
Krátká verze
Můžete to zjednodušit tento dotaz. CTE ve výše uvedeném dotazu je pouze pro vzdělávací účely:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Upřesnit pomocí rCTE
Zvláště pokud si nejste tak jisti mezerami a odhady.
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
Umíme pracovat smenším přebytkem v základním dotazu. Pokud je příliš mnoho mezer, takže v první iteraci nenajdeme dostatek řádků, rCTE pokračuje v iteraci s rekurzivním členem. Stále potřebujeme relativně málo mezery v prostoru ID nebo rekurze mohou vyschnout před dosažením limitu – nebo musíme začít s dostatečně velkou vyrovnávací pamětí, která odporuje účelu optimalizace výkonu.
Duplikáty jsou eliminovány UNION
v rCTE.
Vnější LIMIT
zastaví CTE, jakmile budeme mít dostatek řádků.
Tento dotaz je pečlivě navržen tak, aby používal dostupný index, generoval skutečně náhodné řádky a nezastavil se, dokud nesplníme limit (pokud rekurze nevyschne). Pokud se ho chystáte přepsat, je zde řada úskalí.
Zabalit do funkce
Pro opakované použití s různými parametry:
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$;
Volejte:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Můžete dokonce vytvořit toto obecné, aby fungovalo pro jakoukoli tabulku:Vezměte název sloupce PK a tabulky jako polymorfní typ a použijte EXECUTE
... Ale to je nad rámec této otázky. Viz:
- Refaktorujte funkci PL/pgSQL tak, aby vrátila výstup různých SELECT dotazů
Možná alternativa
POKUD vaše požadavky umožňují identické sady pro opakování hovory (a to mluvíme o opakovaných hovorech) bych zvážil materializovaný pohled . Proveďte jednou výše uvedený dotaz a zapište výsledek do tabulky. Uživatelé získají kvazi náhodný výběr rychlostí osvětlení. Obnovte svůj náhodný výběr v intervalech nebo událostech dle vašeho výběru.
Postgres 9.5 zavádí TABLESAMPLE SYSTEM (n)
Kde n
je procento. Manuál:
BERNOULLI
aSYSTEM
vzorkovací metody každá akceptuje jediný argument, což je zlomek tabulky k vzorku, vyjádřený jakoprocento mezi 0 a 100 . Tento argument může být jakýkolireal
-hodnotný výraz.
Odvážný důraz můj. Je to velmi rychlé , ale výsledek není úplně náhodný . Znovu manuál:
SYSTEM
metoda je výrazně rychlejší nežBERNOULLI
metoda, kdy jsou specifikována malá procenta vzorkování, ale může vrátit méně náhodný vzorek tabulky jako výsledek shlukování.
Počet vrácených řádků se může výrazně lišit. Pro náš příklad, abychom dostali přibližně 1000 řádků:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Související:
- Rychlý způsob, jak zjistit počet řádků tabulky v PostgreSQL
Nebo nainstalujte další modul tsm_system_rows získat přesný počet požadovaných řádků (pokud je jich dost) a umožnit pohodlnější syntaxi:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Podrobnosti viz Evanova odpověď.
Ale to stále není úplně náhodné.