Se me ocurrió una solución muy rápida sin TABLESAMPLE. Mucho más rápido que OFFSET random()*N LIMIT 1. Ni siquiera requiere contar la mesa.
La idea es crear un índice de expresión con datos aleatorios pero predecibles, por ejemplo md5(primary key).
Aquí hay una prueba con datos de muestra de 1 millón de filas:
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;
Resultado:
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
Esta consulta puede a veces (con una probabilidad de 1 / Number_of_rows) devolver 0 filas, por lo que es necesario verificarla y volver a ejecutarla. Además, las probabilidades no son exactamente las mismas: algunas filas son más probables que otras.
Para comparacion:
explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;
Los resultados varían ampliamente, pero pueden ser bastante malos:
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)