Il modo migliore per selezionare le righe casuali di PostgreSQL
performance random (8)
postgresql ordina per casuale (), seleziona le righe in ordine casuale:
select your_columns from your_table ORDER BY random()
postgresql ordina per random () con un distinto:
select * from
(select distinct your_columns from your_table) table_alias
ORDER BY random()
postgresql ordina per limite casuale una riga:
select your_columns from your_table ORDER BY random() limit 1
Voglio una selezione casuale di righe in PostgreSQL, ho provato questo:
select * from table where random() < 0.01;
Ma alcuni altri raccomandano questo:
select * from table order by random() limit 1000;
Ho un tavolo molto grande con 500 milioni di file, voglio che sia veloce.
Quale approccio è migliore? Quali sono le differenze? Qual è il modo migliore per selezionare le righe casuali?
È possibile esaminare e confrontare il piano di esecuzione di entrambi utilizzando
EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;
Un rapido test su un grande tavolo 1 mostra che ORDER BY
ordina prima la tabella completa e quindi preleva i primi 1000 elementi. L'ordinamento di una tabella di grandi dimensioni non solo legge quella tabella ma comporta anche la lettura e la scrittura di file temporanei. where random() < 0.1
esegue solo una scansione della tabella completa una volta sola.
Per le tabelle di grandi dimensioni questo potrebbe non essere ciò che si desidera, poiché anche una scansione completa della tabella potrebbe richiedere molto tempo.
Una terza proposta sarebbe
select * from table where random() < 0.01 limit 1000;
Questo interrompe la scansione della tabella non appena sono state trovate 1000 righe e quindi torna prima. Ovviamente questo impallidisce un po 'la casualità, ma forse questo è abbastanza buono nel tuo caso.
Modifica: Oltre a queste considerazioni, è possibile verificare le domande già poste per questo. L'utilizzo della query [postgresql] random
restituisce alcuni colpi in modo [postgresql] random
.
- selezione rapida delle righe casuali in Postgres
- Come recuperare le righe di dati randomizzati da una tabella postgreSQL?
- postgres: ottiene voci casuali dalla tabella - troppo lento
E un articolo collegato di depez che delinea diversi altri approcci:
1 "grande" come in "il tavolo completo non si adatterà alla memoria".
Aggiungi una colonna chiamata r
con tipo serial
. Indice r
.
Supponiamo di avere 200.000 righe, genereremo un numero casuale n
, dove 0 < n
<= 200, 000.
Seleziona le righe con r > n
, ordinali ASC
e seleziona il più piccolo.
Codice:
select * from YOUR_TABLE
where r > (
select (
select reltuples::bigint AS estimate
from pg_class
where oid = 'public.YOUR_TABLE'::regclass) * random()
)
order by r asc limit(1);
Il codice è auto-esplicativo. La sottoquery al centro viene utilizzata per stimare rapidamente i conteggi delle righe della tabella da https://.com/a/7945274/1271094 .
Nel livello applicazione è necessario eseguire nuovamente l'istruzione se n
> il numero di righe o se è necessario selezionare più righe.
Date le vostre specifiche (più informazioni aggiuntive nei commenti),
- Hai una colonna ID numerica (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 serve bene.
La query seguente non richiede una scansione sequenziale della tabella grande, ma 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 possibilmente costosa è il count(*)
(per tabelle enormi). Date le specifiche sopra, non ne hai bisogno. Una stima andrà bene, disponibile quasi senza alcun costo ( 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 spazio
id
. Hai "poche lacune", quindi aggiungi il 10% (sufficiente 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 agli
id
al grande tavolo. Questo dovrebbe essere molto veloce con l'indice in atto.Infine, ritaglia i surplus
id
che non sono stati mangiati dai dotti e dagli spazi vuoti. Ogni riga ha una probabilità completamente uguale di essere selezionata.
Versione breve
Puoi semplificare questa query. Il CTE nella query sopra è solo per scopi didattici:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Affina 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 più piccolo nella query di base. Se ci sono troppe lacune in modo da non trovare abbastanza righe nella prima iterazione, la rCTE continua ad iterare con il termine ricorsivo. Abbiamo ancora bisogno di un numero relativamente ridotto di spazi nello spazio ID o la ricorsione potrebbe esaurirsi prima che venga raggiunto il limite - oppure dobbiamo iniziare con un buffer sufficientemente grande che non consente di ottimizzare le prestazioni.
I duplicati vengono eliminati dall'UNION nella rCTE.
Il LIMIT
esterno rende il CTE fermo non appena abbiamo abbastanza righe.
Questa query viene accuratamente elaborata per utilizzare l'indice disponibile, generare in realtà righe casuali e non fermarsi finché non rispettiamo il limite (a meno che la ricorsione non funzioni a secco). Ci sono un certo numero di insidie qui se hai intenzione di riscriverlo.
Avvolgere 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 addirittura rendere questo generico funzionare per qualsiasi tabella: prendi il nome della colonna PK e della tabella come tipo polimorfo e usa EXECUTE
... Ma questo va oltre lo scopo di questa domanda. Vedere:
Alternativa possibile
SE i tuoi requisiti consentono serie identiche per chiamate ripetute (e stiamo parlando di chiamate ripetute) considererei una vista materializzata . Esegui sopra una query e scrivi il risultato su una tabella. Gli utenti ottengono una selezione quasi casuale alla velocità della luce. Aggiorna la scelta casuale a intervalli o eventi di tua scelta.
Postgres 9.5 introduce il TABLESAMPLE SYSTEM (n)
È molto veloce , ma il risultato non è esattamente casuale . Il manuale:
Il metodo
SYSTEM
è significativamente più veloce del metodoBERNOULLI
quando vengono specificate piccole percentuali di campionamento, ma può restituire un campione meno casuale della tabella come risultato degli effetti di clustering.
E il numero di righe restituite può variare notevolmente. Per il nostro esempio, per ottenere circa 1000 righe, prova:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Dove n è una percentuale Il manuale:
I metodi di campionamento
BERNOULLI
eSYSTEM
accettano ciascuno un singolo argomento che è la frazione della tabella da campionare, espressa in percentuale tra 0 e 100 . Questo argomento può essere qualsiasi espressione valutata in modoreal
.
Grassetto enfasi mio.
Relazionato:
Oppure installa il modulo aggiuntivo tsm_system_rows per ottenere esattamente il numero di righe richieste (se ce ne sono abbastanza) e consenti la sintassi più comoda:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Vedi la risposta di Evan per i dettagli.
Ma non è ancora esattamente casuale.
Ecco una decisione che funziona per me. Immagino che sia molto semplice da capire ed eseguire.
SELECT
field_1,
field_2,
field_2,
random() as ordering
FROM
big_table
WHERE
some_conditions
ORDER BY
ordering
LIMIT 1000;
Quello con l'ordine BY sarà il più lento.
select * from table where random() < 0.01;
va registrato per record e decide di filtrarlo casualmente oppure no. Questo sarà O(N)
perché deve solo controllare ogni record una volta.
select * from table order by random() limit 1000;
sta per ordinare l'intero tavolo, quindi scegliere il primo 1000. A parte qualsiasi magia voodoo dietro le quinte, l'ordine di è O(N * log N)
.
Lo svantaggio di quello random() < 0.01
è che otterrai un numero variabile di record di output.
Nota, c'è un modo migliore per mischiare un set di dati rispetto all'ordinamento casuale: The Fisher-Yates Shuffle , che viene eseguito in O(N)
. Implementare lo shuffle in SQL sembra comunque una sfida.
So di essere un po 'in ritardo per la festa, ma ho appena trovato questo fantastico strumento chiamato pg_sample .
Ho provato questo con un database di 350M righe ed è stato molto veloce, non so sulla casualità .
./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
Una variante della vista materializzata "Possibile alternativa" delineata da Erwin Brandstetter è possibile.
Supponiamo, ad esempio, di non volere duplicati nei valori randomizzati che vengono restituiti. Quindi sarà necessario impostare un valore booleano sulla tabella primaria contenente il set di valori (non randomizzato).
Supponendo che questa sia la tabella di input:
id_values id | used
----+--------
1 | FALSE
2 | FALSE
3 | FALSE
4 | FALSE
5 | FALSE
...
ID_VALUES
tabella ID_VALUES
secondo necessità. Quindi, come descritto da Erwin, crea una vista materializzata che randomizza la tabella ID_VALUES
una volta:
CREATE MATERIALIZED VIEW id_values_randomized AS
SELECT id
FROM id_values
ORDER BY random();
Si noti che la vista materializzata non contiene la colonna usata, perché questa diventerà presto obsoleta. Né la vista deve contenere altre colonne che possono trovarsi nella tabella id_values
.
Per ottenere (e "consumare") valori casuali, utilizzare un UPDATE-RETURNING su id_values
, selezionare id_values
da id_values_randomized
con un join e applicare i criteri desiderati per ottenere solo le possibilità rilevanti. Per esempio:
UPDATE id_values
SET used = TRUE
WHERE id_values.id IN
(SELECT i.id
FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id
WHERE (NOT i.used)
LIMIT 5)
RETURNING id;
Cambia LIMIT
se necessario: se hai solo bisogno di un valore casuale alla volta, modifica il LIMIT
a 1
.
Con gli indici appropriati su id_values
, credo che l'UPDATE-RETURNING dovrebbe essere eseguito molto rapidamente con poco carico. Restituisce valori randomizzati con un round-trip del database. I criteri per le righe "idonee" possono essere complessi come richiesto. È possibile aggiungere nuove righe alla tabella id_values
in qualsiasi momento e diventeranno accessibili all'applicazione non appena viene aggiornata la vista materializzata (che probabilmente può essere eseguita in un momento non di punta). La creazione e l'aggiornamento della vista materializzata saranno lenti, ma deve essere eseguito solo quando nuovi ID vengono aggiunti alla tabella id_values
.