I betragtning af dine specifikationer (plus yderligere oplysninger i kommentarerne),
- Du har en numerisk id-kolonne (heltal) med kun få (eller moderat få) huller.
- Tydeligvis ingen eller få skriveoperationer.
- Din ID-kolonne skal indekseres! En primær nøgle tjener fint.
Forespørgslen nedenfor kræver ikke en sekventiel scanning af den store tabel, kun en indeksscanning.
Få først estimater for hovedforespørgslen:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Den eneste muligvis dyre del er count(*)
(til store borde). Givet ovenstående specifikationer, har du ikke brug for det. Et skøn vil fungere fint, tilgængeligt næsten uden omkostninger (detaljeret forklaring her):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Så længe ct
er ikke meget mindre end id_span
, vil forespørgslen overgå andre tilgange.
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
-
Generer tilfældige tal i
id
plads. Du har "få mellemrum", så tilføj 10 % (nok til nemt at dække de tomme felter) til antallet af rækker, der skal hentes. -
Hvert
id
kan vælges flere gange tilfældigt (dog meget usandsynligt med et stort id-rum), så grupper de genererede tal (eller brugDISTINCT
). -
Deltag i
id
s til det store bord. Dette bør være meget hurtigt med indekset på plads. -
Trim endelig overskydende
id
s, der ikke er blevet spist af duper og huller. Hver række har en fuldstændig lige chance skal plukkes.
Kort version
Du kan forenkle denne forespørgsel. CTE i forespørgslen ovenfor er kun til uddannelsesformål:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Forfin med rCTE
Især hvis du ikke er så sikker på huller og skøn.
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
Vi kan arbejde med et mindre overskud i basisforespørgslen. Hvis der er for mange huller, så vi ikke finder nok rækker i den første iteration, fortsætter rCTE med at iterere med det rekursive led. Vi mangler stadig relativt få huller i ID-rummet eller rekursionen kan løbe tør, før grænsen er nået - eller vi skal starte med en tilstrækkelig stor buffer, som trodser formålet med at optimere ydeevnen.
Dubletter elimineres af UNION
i rCTE.
Den ydre LIMIT
får CTE til at stoppe, så snart vi har nok rækker.
Denne forespørgsel er omhyggeligt udarbejdet for at bruge det tilgængelige indeks, generere faktisk tilfældige rækker og ikke stoppe, før vi opfylder grænsen (medmindre rekursionen løber tør). Der er en række faldgruber her, hvis du skal omskrive det.
Brug ind i funktion
Til gentagen brug med varierende parametre:
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$;
Ring til:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Du kan endda få denne generiske til at fungere for enhver tabel:Tag navnet på PK-kolonnen og tabellen som polymorf type og brug EXECUTE
... Men det er uden for dette spørgsmåls rammer. Se:
- Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler
Muligt alternativ
HVIS dine krav tillader identiske sæt for gentagne opkald (og vi taler om gentagne opkald) Jeg vil overveje en materialiseret opfattelse . Udfør ovenstående forespørgsel én gang, og skriv resultatet til en tabel. Brugere får et næsten tilfældigt udvalg med lynhastighed. Opdater dit tilfældige valg med intervaller eller begivenheder efter eget valg.
Postgres 9.5 introducerer TABLESAMPLE SYSTEM (n)
Hvor n
er en procentdel. Manualen:
BERNOULLI
og SYSTEM
stikprøvemetoder accepterer hver et enkeltargument, som er den brøkdel af tabellen, der skal prøves, udtrykt som enprocentdel mellem 0 og 100 . Dette argument kan være en hvilken som helst real
-vurderet udtryk.
Fed fremhævelse min. Det er meget hurtigt , men resultatet er ikke ligefrem tilfældigt . Manualen igen:
SYSTEM
metoden er betydeligt hurtigere end BERNOULLI
metoden, når der er angivet små stikprøveprocenter, men den kan returnere en mindre tilfældig stikprøve af tabellen som følge af klyngeeffekter.
Antallet af returnerede rækker kan variere voldsomt. For vores eksempel, for at få omtrent 1000 rækker:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Relateret:
- Hurtig måde at opdage rækkeantallet i en tabel i PostgreSQL
Eller installer det ekstra modul tsm_system_rows for at få antallet af anmodede rækker nøjagtigt (hvis der er nok) og tillade den mere bekvemme syntaks:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Se Evans svar for detaljer.
Men det er stadig ikke helt tilfældigt.