sql >> Database teknologi >  >> RDS >> PostgreSQL

Bedste måde at vælge tilfældige rækker PostgreSQL

I betragtning af dine specifikationer (plus yderligere oplysninger i kommentarerne),

  • Du har en numerisk id-kolonne (heltal) med kun få (eller moderat få) huller.
  • Tydeligvis ingen eller få skriveoperationer.
  • Din ID-kolonne skal indekseres! En primær nøgle tjener fint.

Forespørgslen nedenfor kræver ikke en sekventiel scanning af den store tabel, kun en indeksscanning.

Få først estimater for hovedforespørgslen:

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 eneste muligvis dyre del er count(*) (til store borde). Givet ovenstående specifikationer, har du ikke brug for det. Et skøn vil fungere fint, tilgængeligt næsten uden omkostninger (detaljeret forklaring her):

SELECT reltuples AS ct FROM pg_class
WHERE oid = 'schema_name.big'::regclass;

Så længe ct er ikke meget mindre end id_span , vil forespørgslen overgå andre tilgange.

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
  • Generer tilfældige tal i id plads. Du har "få mellemrum", så tilføj 10 % (nok til nemt at dække de tomme felter) til antallet af rækker, der skal hentes.

  • Hvert id kan vælges flere gange tilfældigt (dog meget usandsynligt med et stort id-rum), så grupper de genererede tal (eller brug DISTINCT ).

  • Deltag i id s til det store bord. Dette bør være meget hurtigt med indekset på plads.

  • Trim endelig overskydende id s, der ikke er blevet spist af duper og huller. Hver række har en fuldstændig lige chance skal plukkes.

Kort version

Du kan forenkle denne forespørgsel. CTE i forespørgslen ovenfor er kun til uddannelsesformå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;

Forfin med rCTE

Især hvis du ikke er så sikker på huller og skøn.

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 arbejde med et mindre overskud i basisforespørgslen. Hvis der er for mange huller, så vi ikke finder nok rækker i den første iteration, fortsætter rCTE med at iterere med det rekursive led. Vi mangler stadig relativt huller i ID-rummet eller rekursionen kan løbe tør, før grænsen er nået - eller vi skal starte med en tilstrækkelig stor buffer, som trodser formålet med at optimere ydeevnen.

Dubletter elimineres af UNION i rCTE.

Den ydre LIMIT får CTE til at stoppe, så snart vi har nok rækker.

Denne forespørgsel er omhyggeligt udarbejdet for at bruge det tilgængelige indeks, generere faktisk tilfældige rækker og ikke stoppe, før vi opfylder grænsen (medmindre rekursionen løber tør). Der er en række faldgruber her, hvis du skal omskrive det.

Brug ind i funktion

Til gentagen brug med varierende parametre:

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 til:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);

Du kan endda få denne generiske til at fungere for enhver tabel:Tag navnet på PK-kolonnen og tabellen som polymorf type og brug EXECUTE ... Men det er uden for dette spørgsmåls rammer. Se:

  • Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler

Muligt alternativ

HVIS dine krav tillader identiske sæt for gentagne opkald (og vi taler om gentagne opkald) Jeg vil overveje en materialiseret opfattelse . Udfør ovenstående forespørgsel én gang, og skriv resultatet til en tabel. Brugere får et næsten tilfældigt udvalg med lynhastighed. Opdater dit tilfældige valg med intervaller eller begivenheder efter eget valg.

Postgres 9.5 introducerer TABLESAMPLE SYSTEM (n)

Hvor n er en procentdel. Manualen:

BERNOULLI og SYSTEM stikprøvemetoder accepterer hver et enkeltargument, som er den brøkdel af tabellen, der skal prøves, udtrykt som enprocentdel mellem 0 og 100 . Dette argument kan være en hvilken som helst real -vurderet udtryk.

Fed fremhævelse min. Det er meget hurtigt , men resultatet er ikke ligefrem tilfældigt . Manualen igen:

SYSTEM metoden er betydeligt hurtigere end BERNOULLI metoden, når der er angivet små stikprøveprocenter, men den kan returnere en mindre tilfældig stikprøve af tabellen som følge af klyngeeffekter.

Antallet af returnerede rækker kan variere voldsomt. For vores eksempel, for at få omtrent 1000 rækker:

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);

Relateret:

  • Hurtig måde at opdage rækkeantallet i en tabel i PostgreSQL

Eller installer det ekstra modul tsm_system_rows for at få antallet af anmodede rækker nøjagtigt (hvis der er nok) og tillade den mere bekvemme syntaks:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

Se Evans svar for detaljer.

Men det er stadig ikke helt tilfældigt.



  1. Sådan installeres Neo4j på Ubuntu 20.04

  2. Påvirker Sql JOIN-ordren ydeevnen?

  3. Den nemmeste måde til PHP-e-mailbekræftelseslink

  4. Evalueringsrækkefølge for Oracle SQL-sætning