Biorąc pod uwagę twoje specyfikacje (plus dodatkowe informacje w komentarzach),
- Masz kolumnę numerycznego identyfikatora (liczby całkowite) z tylko kilkoma (lub umiarkowanie kilkoma) przerwami.
- Oczywiście brak lub kilka operacji zapisu.
- Twoja kolumna ID musi być zindeksowana! Klucz podstawowy dobrze służy.
Poniższe zapytanie nie wymaga sekwencyjnego skanowania dużej tabeli, tylko skanowania indeksu.
Najpierw uzyskaj szacunki dla głównego zapytania:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Jedyną potencjalnie kosztowną częścią jest count(*)
(dla ogromnych stołów). Biorąc pod uwagę powyższe specyfikacje, nie potrzebujesz tego. Szacunek będzie w porządku, dostępny prawie za darmo (szczegółowe wyjaśnienie tutaj):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Tak długo, jak ct
to nie dużo mniejszy niż id_span
, zapytanie będzie lepsze niż inne podejścia.
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
-
Generuj losowe liczby w
id
przestrzeń. Masz „kilka luk”, więc dodaj 10% (wystarczająco, aby łatwo zakryć puste miejsca) do liczby rzędów do pobrania. -
Każdy
id
mogą być wybierane wielokrotnie przez przypadek (choć bardzo mało prawdopodobne przy dużej przestrzeni identyfikatora), więc zgrupuj wygenerowane liczby (lub użyjDISTINCT
). -
Dołącz do
id
s do dużego stołu. Powinno to nastąpić bardzo szybko z włożonym indeksem. -
Na koniec przytnij nadmiar
id
s, które nie zostały zjedzone przez naśladowców i luki. Każdy rząd ma całkowicie równe szanse do wyboru.
Krótka wersja
Możesz uprościć to zapytanie. CTE w powyższym zapytaniu służy tylko do celów edukacyjnych:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Doprecyzuj za pomocą rCTE
Zwłaszcza jeśli nie masz pewności co do luk i szacunków.
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
)
TABLE random_pick
LIMIT 1000; -- actual limit
Możemy pracować z mniejszą nadwyżką w zapytaniu podstawowym. Jeśli jest zbyt wiele luk, więc nie znajdziemy wystarczającej liczby wierszy w pierwszej iteracji, rCTE kontynuuje iterację z terminem rekurencyjnym. Nadal potrzebujemy stosunkowo niewielu luki w przestrzeni ID lub rekurencja mogą wyschnąć przed osiągnięciem limitu - lub musimy zacząć od wystarczająco dużego bufora, który jest sprzeczny z celem optymalizacji wydajności.
Duplikaty są eliminowane przez UNION
w rCTE.
Zewnętrzny LIMIT
powoduje zatrzymanie CTE, gdy tylko mamy wystarczającą liczbę wierszy.
To zapytanie jest starannie opracowane, aby korzystać z dostępnego indeksu, generować faktycznie losowe wiersze i nie zatrzymywać się, dopóki nie osiągniemy limitu (chyba że rekurencja wyschnie). Istnieje wiele pułapek, jeśli zamierzasz to przepisać.
Zawijaj w funkcję
Do wielokrotnego użytku z różnymi parametrami:
CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
RETURNS SETOF big
LANGUAGE plpgsql VOLATILE ROWS 1000 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
)
TABLE random_pick
LIMIT _limit;
END
$func$;
Zadzwoń:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Możesz nawet sprawić, by ta ogólna działała dla dowolnej tabeli:weź nazwę kolumny PK i tabeli jako typ polimorficzny i użyj EXECUTE
... Ale to wykracza poza zakres tego pytania. Zobacz:
- Refaktoryzuj funkcję PL/pgSQL, aby zwrócić dane wyjściowe różnych zapytań SELECT
Możliwa alternatywa
JEŚLI Twoje wymagania pozwalają identyczne zestawy dla powtarzających się połączeń (i mówimy o powtarzających się połączeniach) Rozważałbym widok zmaterializowany . Wykonaj powyższe zapytanie raz i zapisz wynik do tabeli. Użytkownicy otrzymują quasi losowy wybór w błyskawicznym tempie. Odśwież swój losowy wybór w określonych odstępach czasu lub wydarzeniach.
Postgres 9.5 wprowadza TABLESAMPLE SYSTEM (n)
Gdzie n
to procent. Instrukcja:
BERNOULLI
i SYSTEM
każda z metod próbkowania akceptuje pojedynczy argument, który jest ułamkiem tabeli do próbkowania, wyrażony jakoprocent od 0 do 100 . Ten argument może być dowolnym real
wyrażenie wartościowe.
Moje odważne podkreślenie. Jest bardzo szybki , ale wynik jest niezupełnie przypadkowy . Instrukcja ponownie:
SYSTEM
metoda jest znacznie szybsza niż BERNOULLI
metodagdy określone są małe wartości procentowe próbkowania, ale może zwrócić mniej losową próbkę tabeli w wyniku efektów grupowania.
Liczba zwracanych wierszy może się bardzo różnić. Na przykład, aby uzyskać z grubsza 1000 wierszy:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Powiązane:
- Szybki sposób na sprawdzenie liczby wierszy tabeli w PostgreSQL
Lub zainstaluj dodatkowy moduł tsm_system_rows aby uzyskać dokładną liczbę żądanych wierszy (jeśli jest ich wystarczająca) i zezwolić na wygodniejszą składnię:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Zobacz odpowiedź Evana, aby poznać szczegóły.
Ale to wciąż nie jest dokładnie losowe.