Ich habe eine sehr schnelle Lösung ohne gefunden TABLESAMPLE. Viel schneller als OFFSET random()*N LIMIT 1. Es erfordert nicht einmal die Anzahl der Tabellen.
Die Idee ist beispielsweise, einen Ausdrucksindex mit zufälligen, aber vorhersehbaren Daten zu erstellen md5(primary key).
Hier ist ein Test mit Beispieldaten für 1 Million Zeilen:
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;
Ergebnis:
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
Diese Abfrage kann manchmal (mit einer Wahrscheinlichkeit von ungefähr 1 / Anzahl_der_Zeilen) 0 Zeilen zurückgeben, daher muss sie überprüft und erneut ausgeführt werden. Auch die Wahrscheinlichkeiten sind nicht genau gleich - einige Zeilen sind wahrscheinlicher als andere.
Zum Vergleich:
explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;
Die Ergebnisse variieren stark, können aber ziemlich schlecht sein:
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)