Med tanke på dina specifikationer (plus ytterligare information i kommentarerna),
- Du har en numerisk ID-kolumn (heltal) med endast få (eller måttligt få) luckor.
- Självklart inga eller få skrivoperationer.
- Din ID-kolumn måste indexeras! En primärnyckel fungerar bra.
Frågan nedan behöver inte en sekventiell genomsökning av den stora tabellen, bara en indexskanning.
Få först uppskattningar för huvudfrågan:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Den enda möjligen dyra delen är count(*)
(för stora bord). Med tanke på ovanstående specifikationer behöver du det inte. En uppskattning kommer att fungera bra, tillgänglig nästan utan kostnad (detaljerad förklaring här):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Så länge som ct
är inte mycket mindre än id_span
, kommer frågan att överträffa andra tillvägagångssätt.
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
-
Generera slumpmässiga tal i
id
Plats. Du har "få luckor", så lägg till 10 % (tillräckligt för att enkelt täcka de tomma områdena) till antalet rader som ska hämtas. -
Varje
id
kan väljas flera gånger av en slump (dock mycket osannolikt med ett stort id-utrymme), så gruppera de genererade talen (eller användDISTINCT
). -
Gå med i
id
s till det stora bordet. Detta bör vara mycket snabbt med indexet på plats. -
Slutligen trimma överskott
id
s som inte har blivit uppätna av duper och luckor. Varje rad har en helt lika chans ska plockas.
Kort version
Du kan förenkla denna fråga. CTE i frågan ovan är bara för utbildningsändamål:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Förfina med rCTE
Speciellt om du inte är så säker på luckor och uppskattningar.
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
Vi kan arbeta med ett mindre överskott i basfrågan. Om det finns för många luckor så att vi inte hittar tillräckligt många rader i den första iterationen, fortsätter rCTE att iterera med den rekursiva termen. Vi behöver fortfarande relativt få luckor i ID-utrymmet eller rekursionen kan torka innan gränsen nås - eller så måste vi börja med en tillräckligt stor buffert som trotsar syftet med att optimera prestandan.
Dubbletter elimineras av UNION
i rCTE.
Den yttre LIMIT
gör att CTE slutar så fort vi har tillräckligt med rader.
Den här frågan är noggrant utformad för att använda det tillgängliga indexet, generera faktiskt slumpmässiga rader och inte sluta förrän vi uppfyller gränsen (såvida inte rekursionen är torr). Det finns ett antal fallgropar här om du ska skriva om det.
Lägg in i funktion
För upprepad användning med varierande parametrar:
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$;
Ring:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Du kan till och med få detta generiskt att fungera för vilken tabell som helst:Ta namnet på PK-kolumnen och tabellen som polymorf typ och använd EXECUTE
... Men det ligger utanför ramen för denna fråga. Se:
- Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor
Möjligt alternativ
OM dina krav tillåter identiska uppsättningar för upprepade samtal (och vi pratar om upprepade samtal) Jag skulle överväga en materialiserad syn . Kör ovanstående fråga en gång och skriv resultatet till en tabell. Användare får ett nästan slumpmässigt urval med blixthastighet. Uppdatera ditt slumpmässiga val med intervaller eller evenemang som du väljer.
Postgres 9.5 introducerar TABLESAMPLE SYSTEM (n)
Där n
är en procentsats. Manualen:
BERNOULLI
ochSYSTEM
Samplingsmetoderna accepterar vart och ett av ett enda argument som är den del av tabellen som ska provas, uttryckt som enprocentandel mellan 0 och 100 . Detta argument kan vara vilkenreal
som helst -värderade uttryck.
Djärv betoning min. Det är mycket snabbt , men resultatet är inte exakt slumpmässigt . Manualen igen:
SYSTEM
metoden är betydligt snabbare änBERNOULLI
metod när små samplingsprocenter anges, men den kan returnera ett mindre slumpmässigt urval av tabellen som ett resultat av klustringseffekter.
Antalet rader som returneras kan variera kraftigt. För vårt exempel, för att få ungefär 1000 rader:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Relaterat:
- Snabbt sätt att upptäcka radantalet i en tabell i PostgreSQL
Eller installera tilläggsmodulen tsm_system_rows för att få exakt antalet begärda rader (om det finns tillräckligt många) och tillåta den mer bekväma syntaxen:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Se Evans svar för detaljer.
Men det är fortfarande inte helt slumpmässigt.