sql >> Database >  >> RDS >> PostgreSQL

Beste manier om willekeurige rijen te selecteren PostgreSQL

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 gebruik DISTINCT ).

  • 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 en SYSTEM 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 elke real . 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 de BERNOULLI 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.



  1. PostgreSQL-anonimisering op aanvraag

  2. Rijen dynamisch in kolommen draaien in Oracle

  3. Bestel een MySQL-tabel met twee kolommen

  4. Ontdek 10 minder bekende mogelijkheden van SQL Diagnostic Manager