Date le vostre specifiche (più informazioni aggiuntive nei commenti),
- Hai una colonna ID numerica (numeri interi) con solo poche (o moderatamente poche) lacune.
- Ovviamente nessuna o poche operazioni di scrittura.
- La colonna del tuo ID deve essere indicizzata! Una chiave primaria serve bene.
La query seguente non richiede una scansione sequenziale della tabella grande, ma solo una scansione dell'indice.
Innanzitutto, ottieni 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 è count(*)
(per tavoli enormi). Dati sopra indicati, non è necessario. 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 più piccolo 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 nello id
spazio. Hai "poche lacune", quindi aggiungi il 10% (abbastanza per coprire facilmente gli spazi) al numero di righe da recuperare.
Ognuno id
può essere scelto più volte per caso (anche se molto improbabile con un grande spazio ID), quindi raggruppa i numeri generati (o usa DISTINCT
).
Unisciti alla id
s al grande tavolo. Questo dovrebbe essere molto veloce con l'indice in atto.
Infine, tagliare le eccedenze id
che non sono state consumate da duplicati e lacune. Ogni riga ha una probabilità completamente uguale di essere selezionata.
Versione breve
È possibile semplificare questa query. Il CTE nella query sopra è solo a scopo educativo:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Filtra 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
)
SELECT *
FROM random_pick
LIMIT 1000; -- actual limit
Possiamo lavorare con un surplus minore nella query di base. Se ci sono troppi vuoti, quindi non troviamo abbastanza righe nella prima iterazione, l'rCTE continua a iterare con il termine ricorsivo. Abbiamo ancora bisogno di relativamente poche lacune nello spazio ID o la ricorsione potrebbe esaurirsi prima che venga raggiunto il limite - oppure dobbiamo iniziare con un buffer abbastanza grande che sfidi lo scopo di ottimizzare le prestazioni.
I duplicati vengono eliminati da UNION
in rCTE.
L'esterno LIMIT
fa arrestare il CTE non appena abbiamo abbastanza file.
Questa query viene elaborata con cura per utilizzare l'indice disponibile, generare righe effettivamente casuali e non fermarsi fino a quando non raggiungiamo il limite (a meno che la ricorsione non si esaurisca). Ci sono un certo numero di insidie qui se hai intenzione di riscriverlo.
Avvolgi in funzione
Per uso ripetuto con parametri variabili:
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;
Chiamata:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Potresti persino fare in modo che questo generico funzioni per qualsiasi tabella: prendi il nome della colonna PK e la tabella come tipo polimorfico e usa EXECUTE
... Ma questo va oltre lo scopo di questa domanda. Vedere:
Alternativa possibile
Se i tuoi requisiti consentono set identici per chiamate ripetute (e stiamo parlando di chiamate ripetute), prenderei in considerazione una visione materializzata . Eseguire una volta la query sopra e scrivere 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 di tua scelta.
Dov'è n
una percentuale. Il manuale:
I metodi di campionamento BERNOULLI
e SYSTEM
accettano ciascuno un singolo argomento che è la frazione della tabella da campionare, espressa in
percentuale tra 0 e 100 . Questo argomento può essere qualsiasi real
espressione valutata.
Enorme enfasi sulla mia. È molto veloce , ma il risultato non è esattamente casuale . Il manuale di nuovo:
Il SYSTEM
metodo è significativamente più veloce del BERNOULLI
metodo quando vengono specificate piccole percentuali di campionamento, ma può restituire un campione meno casuale della tabella a causa degli effetti di clustering.
Il numero di righe restituite può variare notevolmente. Per il nostro esempio, per ottenere circa 1000 righe:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Relazionato:
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.