Je suis venu avec une solution très rapide sans TABLESAMPLE. Beaucoup plus rapide que OFFSET random()*N LIMIT 1. Il ne nécessite même pas de compte de table.
L'idée est de créer un index d'expression avec des données aléatoires mais prévisibles, par exemple md5(primary key).
Voici un test avec des exemples de données de 1M lignes:
create table randtest (id serial primary key, data int not null);
insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);
create index randtest_md5_id_idx on randtest (md5(id::text));
explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;
Résultat:
Limit (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
-> Index Scan using randtest_md5_id_idx on randtest (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
Filter: (md5((id)::text) > md5((random())::text))
Rows Removed by Filter: 1831
Total runtime: 6.245 ms
Cette requête peut parfois (avec une probabilité d'environ 1 / Number_of_rows) renvoyer 0 ligne, elle doit donc être vérifiée et réexécutée. De plus, les probabilités ne sont pas exactement les mêmes - certaines lignes sont plus probables que d'autres.
En comparaison:
explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;
Les résultats varient considérablement, mais peuvent être assez mauvais:
Limit (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
-> Seq Scan on randtest (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
Total runtime: 179.211 ms
(3 rows)