Gezien uw specificaties (plus aanvullende informatie in de opmerkingen),
- Je hebt een numerieke ID-kolom (gehele getallen) met slechts weinig (of redelijk weinig) gaten.
- Natuurlijk geen of weinig schrijfbewerkingen.
- Uw ID-kolom moet worden geïndexeerd! Een primaire sleutel dient goed.
De onderstaande query heeft geen sequentiële scan van de grote tafel nodig, alleen een indexscan.
Krijg eerst schattingen voor de hoofdquery:
SELECT count(*) AS ct -- optional
, min(id) AS min_id
, max(id) AS max_id
, max(id) - min(id) AS id_span
FROM big;
Het enige mogelijk dure onderdeel is de count(*)
(voor grote tafels). Gezien bovenstaande specificaties heb je het niet nodig. Een schatting is prima, beschikbaar tegen bijna geen kosten (gedetailleerde uitleg hier):
SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;
Zolang ct
is niet veel kleiner dan id_span
, zal de zoekopdracht beter presteren dan andere benaderingen.
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
-
Genereer willekeurige getallen in de
id
ruimte. Je hebt "weinig gaten", dus voeg 10% (genoeg om de lege plekken gemakkelijk te dekken) toe aan het aantal rijen dat moet worden opgehaald. -
Elke
id
kan bij toeval meerdere keren worden gekozen (hoewel zeer onwaarschijnlijk met een grote ID-ruimte), dus groepeer de gegenereerde nummers (of gebruikDISTINCT
). -
Word lid van de
id
s naar de grote tafel. Dit zou heel snel moeten gaan met de index op zijn plaats. -
Knip tenslotte overtollige
id
af s die niet zijn opgegeten door dupes en gaten. Elke rij heeft een volledig gelijke kans te kiezen.
Korte versie
U kunt vereenvoudigen deze vraag. De CTE in de bovenstaande zoekopdracht is alleen voor educatieve doeleinden:
SELECT *
FROM (
SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
FROM generate_series(1, 1100) g
) r
JOIN big USING (id)
LIMIT 1000;
Verfijn met rCTE
Vooral als je niet zo zeker bent van hiaten en schattingen.
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
We kunnen werken met een kleiner overschot in de basisquery. Als er te veel hiaten zijn, zodat we niet genoeg rijen vinden in de eerste iteratie, blijft de rCTE itereren met de recursieve term. We hebben nog relatief weinig nodig hiaten in de ID-ruimte of de recursie kan opdrogen voordat de limiet is bereikt - of we moeten beginnen met een buffer die groot genoeg is die het doel van het optimaliseren van de prestaties tart.
Duplicaten worden verwijderd door de UNION
in de rCTE.
De buitenste LIMIT
zorgt ervoor dat de CTE stopt zodra we genoeg rijen hebben.
Deze query is zorgvuldig opgesteld om de beschikbare index te gebruiken, eigenlijk willekeurige rijen te genereren en niet te stoppen totdat we de limiet hebben bereikt (tenzij de recursie opdroogt). Er zijn hier een aantal valkuilen als je het gaat herschrijven.
Wikkel in functie
Voor herhaald gebruik met verschillende parameters:
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$;
Bel:
SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Je zou dit zelfs generiek kunnen maken om voor elke tabel te werken:neem de naam van de PK-kolom en de tabel als polymorf type en gebruik EXECUTE
... Maar dat valt buiten het bestek van deze vraag. Zie:
- Refactor een PL/pgSQL-functie om de uitvoer van verschillende SELECT-query's te retourneren
Mogelijk alternatief
ALS uw vereisten identieke sets toestaan voor herhaalde oproepen (en we hebben het over herhaalde oproepen) Ik zou een gematerialiseerde mening overwegen . Voer bovenstaande query eenmaal uit en schrijf het resultaat naar een tabel. Gebruikers krijgen een quasi willekeurige selectie met bliksemsnelheid. Vernieuw uw willekeurige keuze met tussenpozen of evenementen naar keuze.
Postgres 9.5 introduceert TABLESAMPLE SYSTEM (n)
Waar n
is een percentage. De handleiding:
De
BERNOULLI
enSYSTEM
steekproefmethoden accepteren elk een enkel argument dat de fractie van de tabel is die moet worden bemonsterd, uitgedrukt als een percentage tussen 0 en 100 . Dit argument kan elkereal
. zijn -waardevolle uitdrukking.
Vetgedrukte nadruk van mij. Het is erg snel , maar het resultaat is niet bepaald willekeurig . Nogmaals de handleiding:
Het
SYSTEM
methode is aanzienlijk sneller dan deBERNOULLI
methode wanneer kleine steekproefpercentages zijn gespecificeerd, maar het kan een minder willekeurige steekproef van de tabel opleveren als gevolg van clusteringseffecten.
Het aantal geretourneerde rijen kan enorm variëren. Voor ons voorbeeld, om ongeveer . te krijgen 1000 rijen:
SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Gerelateerd:
- Snelle manier om het aantal rijen van een tabel in PostgreSQL te ontdekken
Of installeer de extra module tsm_system_rows om het aantal gevraagde rijen precies te krijgen (als er genoeg zijn) en de handiger syntaxis toe te staan:
SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Zie Evans antwoord voor details.
Maar dat is nog steeds niet helemaal willekeurig.