Mam tabelę w postgres, która zawiera kilka milionów wierszy. Sprawdziłem w internecie i znalazłem następujące
SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;
Działa, ale jest naprawdę powolny ... czy istnieje inny sposób wykonania tego zapytania lub bezpośredni sposób na wybranie losowego wiersza bez czytania całej tabeli? Nawiasem mówiąc, „myid” jest liczbą całkowitą, ale może to być puste pole.
Odpowiedzi:
Możesz chcieć poeksperymentować
OFFSET
, jak wSELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
To
N
liczba wierszy wmytable
. Być może będziesz musiał najpierw wykonać a,SELECT COUNT(*)
aby obliczyć wartośćN
.Aktualizacja (Antony Hatchkins)
Musisz użyć
floor
tutaj:SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
Rozważ tabelę z 2 wierszami;
random()*N
generuje0 <= x < 2
i na przykładSELECT myid FROM mytable OFFSET 1.7 LIMIT 1;
zwraca 0 wierszy z powodu niejawnego zaokrąglenia do najbliższej liczby całkowitej.źródło
SELECT COUNT(*)
?, to znaczy, nie używaj wszystkich wartości w tabeli, ale tylko część z nich?EXPLAIN SELECT ...
z różnymi wartościami N daje taki sam koszt dla zapytania, więc myślę, że lepiej jest wybrać maksymalną wartość N.PostgreSQL 9.5 wprowadził nowe podejście do znacznie szybszego wyboru próbek: TABLESAMPLE
Składnia to
SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage); SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);
Nie jest to optymalne rozwiązanie, jeśli chcesz wybrać tylko jeden wiersz, ponieważ musisz znać LICZBĘ tabeli, aby obliczyć dokładną wartość procentową.
Aby uniknąć powolnego COUNT i użyć szybkiego TABLESAMPLE dla tabel od 1 wiersza do miliardów wierszy, możesz wykonać:
SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1; -- if you got no result: SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1; -- if you got no result: SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1; -- if you got no result: SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1; ...
To może nie wyglądać tak elegancko, ale prawdopodobnie jest szybsze niż jakakolwiek inna odpowiedź.
Aby zdecydować, czy chcesz używać BERNULLI oder SYSTEM, przeczytaj o różnicy na http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/
źródło
SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;
?SELECT reltuples FROM pg_class WHERE relname = 'my_table'
do oszacowania liczby.Wypróbowałem to z podzapytaniem i zadziałało dobrze. Offset, przynajmniej w Postgresql v8.4.4 działa dobrze.
select * from mytable offset random() * (select count(*) from mytable) limit 1 ;
źródło
Musisz użyć
floor
:SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;
źródło
random()*N
generuje 0 <= x <2 i na przykładSELECT myid FROM mytable OFFSET 1.7 LIMIT 1;
zwraca 0 wierszy z powodu niejawnego zaokrąglenia do najbliższej liczby całkowitej.order by random()
, w przybliżeniu3*O(N) < O(NlogN)
- rzeczywiste dane dotyczące życia będą się nieco różnić ze względu na wskaźniki.WHERE myid NOT IN (1st-myid)
iWHERE myid NOT IN (1st-myid, 2nd-myid)
nie działają, ponieważ decyzję podejmuje PRZESUNIĘCIE. Hmmm ... Myślę, że mógłbym zmniejszyć N o 1 i 2 w drugim i trzecim SELECT.floor()
? Jakie korzyści daje?Sprawdź ten link, aby uzyskać różne opcje. http://www.depesz.com/index.php/2007/09/16/my-hardts-on-getting-random-row/
Aktualizacja: (A. Hatchkins)
Podsumowanie (bardzo) długiego artykułu jest następujące.
Autor wymienia cztery podejścia:
1)
ORDER BY random() LIMIT 1;
- wolno2)
ORDER BY id where id>=random()*N LIMIT 1
- niejednolite, jeśli występują luki3) kolumna losowa - wymaga od czasu do czasu aktualizacji
4) niestandardowy agregat losowy - przebiegła metoda, może być powolna: random () należy wygenerować N razy
i sugeruje ulepszenie metody nr 2 za pomocą
5)
ORDER BY id where id=random()*N LIMIT 1
z kolejnymi zapytaniami, jeśli wynik jest pusty.źródło
Najłatwiejszym i najszybszym sposobem na pobranie losowego wiersza jest użycie
tsm_system_rows
rozszerzenia:CREATE EXTENSION IF NOT EXISTS tsm_system_rows;
Następnie możesz wybrać dokładną liczbę wierszy, które chcesz:
SELECT myid FROM mytable TABLESAMPLE SYSTEM_ROWS(1);
Jest to dostępne w PostgreSQL 9.5 i nowszych.
Zobacz: https://www.postgresql.org/docs/current/static/tsm-system-rows.html
źródło
ORDER BY random() LIMIT 1;
powinien on być wystarczająco szybki.Wymyśliłem bardzo szybkie rozwiązanie bez
TABLESAMPLE
. Dużo szybciej niżOFFSET random()*N LIMIT 1
. Nie wymaga nawet liczby stolików.Chodzi na przykład o utworzenie indeksu wyrażeń z losowymi, ale przewidywalnymi danymi
md5(primary key)
.Oto test z przykładowymi danymi 1 mln wierszy:
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;
Wynik:
To zapytanie może czasami (z prawdopodobieństwem około 1 / Number_of_rows) zwrócić 0 wierszy, więc należy je sprawdzić i ponownie uruchomić. Również prawdopodobieństwa nie są dokładnie takie same - niektóre wiersze są bardziej prawdopodobne niż inne.
Dla porownania:
explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;
Wyniki są bardzo różne, ale mogą być dość złe:
źródło