sql >> Databáze >  >> RDS >> PostgreSQL

Nejlepší způsob, jak vybrat náhodné řádky PostgreSQL

Vzhledem k vašim specifikacím (plus další informace v komentářích)

  • Máte číselný sloupec ID (celá čísla) s pouze několika (nebo středně málo) mezerami.
  • Zřejmě žádné nebo jen málo operací zápisu.
  • Váš sloupec ID musí být indexován! Primární klíč slouží dobře.

Níže uvedený dotaz nevyžaduje sekvenční skenování velké tabulky, pouze indexové skenování.

Nejprve získejte odhady pro hlavní dotaz:

SELECT count(*) AS ct              -- optional
     , min(id)  AS min_id
     , max(id)  AS max_id
     , max(id) - min(id) AS id_span
FROM   big;

Jedinou možná nákladnou částí je count(*) (pro velké stoly). Vzhledem k výše uvedeným specifikacím to nepotřebujete. Odhad bude fungovat dobře, je k dispozici téměř bez nákladů (podrobné vysvětlení zde):

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

Dokud ct není moc menší než id_span , dotaz předčí ostatní přístupy.

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
  • Generujte náhodná čísla v id prostor. Máte "málo mezer", takže přidejte 10 % (dost na snadné zakrytí prázdných míst) k počtu řádků, které se mají načíst.

  • Každé id lze náhodně vybrat vícekrát (i když je to velmi nepravděpodobné s velkou mezerou ID), proto seskupte vygenerovaná čísla (nebo použijte DISTINCT ).

  • Připojte se k id k velkému stolu. To by mělo být velmi rychlé se zavedeným indexem.

  • Nakonec ořízněte přebytek id s, které nebyly sežrány podvodníky a mezerami. Každý řádek má zcela stejnou šanci k výběru.

Krátká verze

Můžete to zjednodušit tento dotaz. CTE ve výše uvedeném dotazu je pouze pro vzdělávací účely:

SELECT *
FROM  (
   SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
   FROM   generate_series(1, 1100) g
   ) r
JOIN   big USING (id)
LIMIT  1000;

Upřesnit pomocí rCTE

Zvláště pokud si nejste tak jisti mezerami a odhady.

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

Umíme pracovat smenším přebytkem v základním dotazu. Pokud je příliš mnoho mezer, takže v první iteraci nenajdeme dostatek řádků, rCTE pokračuje v iteraci s rekurzivním členem. Stále potřebujeme relativně málo mezery v prostoru ID nebo rekurze mohou vyschnout před dosažením limitu – nebo musíme začít s dostatečně velkou vyrovnávací pamětí, která odporuje účelu optimalizace výkonu.

Duplikáty jsou eliminovány UNION v rCTE.

Vnější LIMIT zastaví CTE, jakmile budeme mít dostatek řádků.

Tento dotaz je pečlivě navržen tak, aby používal dostupný index, generoval skutečně náhodné řádky a nezastavil se, dokud nesplníme limit (pokud rekurze nevyschne). Pokud se ho chystáte přepsat, je zde řada úskalí.

Zabalit do funkce

Pro opakované použití s ​​různými parametry:

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$;

Volejte:

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

Můžete dokonce vytvořit toto obecné, aby fungovalo pro jakoukoli tabulku:Vezměte název sloupce PK a tabulky jako polymorfní typ a použijte EXECUTE ... Ale to je nad rámec této otázky. Viz:

  • Refaktorujte funkci PL/pgSQL tak, aby vrátila výstup různých SELECT dotazů

Možná alternativa

POKUD vaše požadavky umožňují identické sady pro opakování hovory (a to mluvíme o opakovaných hovorech) bych zvážil materializovaný pohled . Proveďte jednou výše uvedený dotaz a zapište výsledek do tabulky. Uživatelé získají kvazi náhodný výběr rychlostí osvětlení. Obnovte svůj náhodný výběr v intervalech nebo událostech dle vašeho výběru.

Postgres 9.5 zavádí TABLESAMPLE SYSTEM (n)

Kde n je procento. Manuál:

BERNOULLI a SYSTEM vzorkovací metody každá akceptuje jediný argument, což je zlomek tabulky k vzorku, vyjádřený jakoprocento mezi 0 a 100 . Tento argument může být jakýkoli real -hodnotný výraz.

Odvážný důraz můj. Je to velmi rychlé , ale výsledek není úplně náhodný . Znovu manuál:

SYSTEM metoda je výrazně rychlejší než BERNOULLI metoda, kdy jsou specifikována malá procenta vzorkování, ale může vrátit méně náhodný vzorek tabulky jako výsledek shlukování.

Počet vrácených řádků se může výrazně lišit. Pro náš příklad, abychom dostali přibližně 1000 řádků:

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

Související:

  • Rychlý způsob, jak zjistit počet řádků tabulky v PostgreSQL

Nebo nainstalujte další modul tsm_system_rows získat přesný počet požadovaných řádků (pokud je jich dost) a umožnit pohodlnější syntaxi:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

Podrobnosti viz Evanova odpověď.

Ale to stále není úplně náhodné.



  1. Problémy s výkonem:První setkání

  2. 3 otázky týkající se monitorování SQL serveru, které je třeba položit při přebírání pozice DBA

  3. Jak zjistit, zda hodnota obsahuje alespoň jedno číslo na serveru SQL

  4. Jak uniknout z jediné citace, speciálních znaků v MySQL