Date le vostre specifiche (più informazioni aggiuntive nei commenti),
- Hai una colonna ID numerico (numeri interi) con solo pochi (o moderatamente pochi) spazi vuoti.
- Ovviamente nessuna o poche operazioni di scrittura.
- La tua colonna ID deve essere indicizzata! Una chiave primaria funziona bene.
La query seguente non richiede una scansione sequenziale della tabella grande, solo una scansione dell'indice.
Innanzitutto, ottieni le stime per la query principale:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
L'unica parte forse costosa è il count(*)
(per tavoli enormi). Date le specifiche sopra, non ne hai bisogno. Un preventivo andrà benissimo, disponibile quasi a costo zero (spiegazione dettagliata qui):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Finché ct
non è molto minore di id_span
, la query supererà gli altri approcci.
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
-
Genera numeri casuali nel
id
spazio. Hai "pochi spazi vuoti", quindi aggiungi il 10% (abbastanza per coprire facilmente gli spazi vuoti) al numero di righe da recuperare. -
Ogni
id
può essere selezionato più volte per caso (anche se molto improbabile con un grande spazio ID), quindi raggruppa i numeri generati (o usaDISTINCT
). -
Unisciti all'
id
s al grande tavolo. Questo dovrebbe essere molto veloce con l'indice in atto. -
Infine taglia l'eccesso
id
s che non sono stati mangiati da duplicati e lacune. Ogni riga ha una opportunità completamente uguale da raccogliere.
Versione breve
Puoi semplificare questa domanda. Il CTE nella query sopra è solo a scopo didattico:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Perfeziona con rCTE
Soprattutto se non sei così sicuro di lacune e stime.
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
Possiamo lavorare con un avanzo inferiore nella query di base. Se ci sono troppi spazi vuoti, quindi non troviamo abbastanza righe nella prima iterazione, rCTE continua a scorrere con il termine ricorsivo. Ne servono ancora relativamente pochi le lacune nello spazio ID o la ricorsione potrebbero esaurirsi prima che venga raggiunto il limite, oppure dobbiamo iniziare con un buffer sufficientemente grande che sfugge allo scopo di ottimizzare le prestazioni.
I duplicati vengono eliminati da UNION
nel rCTE.
Il LIMIT
esterno fa fermare il CTE non appena abbiamo abbastanza righe.
Questa query è stata accuratamente redatta per utilizzare l'indice disponibile, generare effettivamente righe casuali e non interrompersi finché non si raggiunge il limite (a meno che la ricorsione non si esaurisca). Ci sono una serie di insidie qui se hai intenzione di riscriverlo.
Avvolgi nella funzione
Per uso ripetuto con parametri variabili:
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$;
Chiama:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Potresti anche fare in modo che questo generico funzioni per qualsiasi tabella:prendi il nome della colonna PK e della tabella come tipo polimorfico e usa EXECUTE
... Ma questo va oltre lo scopo di questa domanda. Vedi:
- Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT
Possibile alternativa
SE i tuoi requisiti consentono set identici per ripetuti chiamate (e stiamo parlando di chiamate ripetute) io considererei una visione materializzata . Esegui la query sopra una volta e scrivi il risultato in una tabella. Gli utenti ottengono una selezione quasi casuale alla velocità della luce. Aggiorna la tua scelta casuale a intervalli o eventi a tua scelta.
Postgres 9.5 introduce TABLESAMPLE SYSTEM (n)
Dove n
è una percentuale. Il manuale:
Il BERNOULLI
e SYSTEM
ciascuno dei metodi di campionamento accetta un singolo argomento che è la frazione della tabella da campionare, espressa come una percentuale compresa tra 0 e 100 . Questo argomento può essere qualsiasi real
-espressione valutata.
Enfasi in grassetto mio. È molto veloce , ma il risultato è non esattamente casuale . Il manuale di nuovo:
Il SYSTEM
è significativamente più veloce del BERNOULLI
metodoquando vengono specificate piccole percentuali di campionamento, ma può restituire un campione non casuale della tabella a causa degli effetti di raggruppamento.
Il numero di righe restituite può variare notevolmente. Per il nostro esempio, per ottenere approssimativamente 1000 righe:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Correlati:
- Un modo rapido per scoprire il conteggio delle righe di una tabella in PostgreSQL
Oppure installa il modulo aggiuntivo tsm_system_rows per ottenere esattamente il numero di righe richieste (se ce ne sono abbastanza) e consentire la sintassi più conveniente:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Vedi la risposta di Evan per i dettagli.
Ma non è ancora esattamente casuale.