PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Най-добрият начин за избор на произволни редове PostgreSQL

Предвид вашите спецификации (плюс допълнителна информация в коментарите),

  • Имате колона с числови идентификатори (цели числа) само с няколко (или умерено малко) пропуски.
  • Очевидно няма или има няколко операции за запис.
  • Колоната ви за идентификация трябва да бъде индексирана! Първичният ключ служи добре.

Заявката по-долу не се нуждае от последователно сканиране на голямата таблица, а само от сканиране на индекс.

Първо, вземете прогнози за основната заявка:

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

Единствената възможно скъпа част е count(*) (за огромни маси). Предвид горните спецификации, не ви е нужен. Оценката ще свърши работа, достъпна почти безплатно (подробно обяснение тук):

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

Стига ct не е много по-малък от id_span , заявката ще превъзхожда другите подходи.

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
  • Генерирайте произволни числа в id пространство. Имате „няколко пропуски“, така че добавете 10 % (достатъчно, за да покриете лесно празните места) към броя на редовете за извличане.

  • Всеки id могат да бъдат избрани няколко пъти случайно (макар и много малко вероятно с голямо пространство за идентификация), така че групирайте генерираните числа (или използвайте DISTINCT ).

  • Присъединете се към id с до голямата маса. Това трябва да стане много бързо с наличен индекс.

  • Накрая отрежете излишъка id и които не са изядени от измамници и пропуски. Всеки ред има напълно равен шанс да бъде избран.

Кратка версия

Можете даопростите тази заявка. CTE в заявката по-горе е само за образователни цели:

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

Прецизиране с rCTE

Особено ако не сте толкова сигурни в пропуските и оценките.

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

Можем да работим спо-малък излишък в основната заявка. Ако има твърде много пропуски, така че не намерим достатъчно редове в първата итерация, rCTE продължава да итерира с рекурсивния член. Все още имаме нужда от сравнително малко пропуски в идентификационното пространство или рекурсията може да изтече, преди лимитът да бъде достигнат - или трябва да започнем с достатъчно голям буфер, който не отговаря на целта за оптимизиране на производителността.

Дубликатите се елиминират от UNION в rCTE.

Външният LIMIT кара CTE да спре веднага щом имаме достатъчно редове.

Тази заявка е внимателно изготвена, за да използва наличния индекс, да генерира действително произволни редове и да не спира, докато не изпълним ограничението (освен ако рекурсията не свърши). Тук има редица клопки, ако ще го пренапишете.

Увийте във функция

За многократна употреба с различни параметри:

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

Обадете се:

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

Можете дори да направите това общо да работи за всяка таблица:Вземете името на колоната PK и таблицата като полиморфен тип и използвайте EXECUTE ... Но това е извън обхвата на този въпрос. Вижте:

  • Рефакторирайте функция PL/pgSQL, за да върнете резултатите от различни SELECT заявки

Възможна алтернатива

АКО вашите изисквания позволяват еднакви набори за повтарящи се обаждания (и говорим за повтарящи се обаждания) бих помислил за материализиран изглед . Изпълнете горната заявка веднъж и запишете резултата в таблица. Потребителите получават квази случаен избор със скорост на светкавица. Обновявайте произволния си избор на интервали или събития по ваш избор.

Postgres 9.5 въвежда TABLESAMPLE SYSTEM (n)

Където n е процент. Ръководството:

BERNOULLI и SYSTEM всеки метод за вземане на проби приема единичен аргумент, който е частта от таблицата към извадката, изразена катопроцент между 0 и 100 . Този аргумент може да бъде всеки real -ценен израз.

Удебелен акцент мой. Това е много бързо , но резултатът не е точно случаен . Отново ръководството:

SYSTEM методът е значително по-бърз от BERNOULLI метод, когато са посочени малки проценти на извадка, но може да върне безпроизволна извадка от таблицата в резултат на клъстерни ефекти.

Броят на върнатите редове може да варира значително. За нашия пример, за да получите приблизително 1000 реда:

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

Свързано:

  • Бърз начин да откриете броя на редовете на таблица в PostgreSQL

Или инсталирайте допълнителния модул tsm_system_rows за да получите точно броя на исканите редове (ако има достатъчно) и да позволите по-удобния синтаксис:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

Вижте отговора на Евън за подробности.

Но това все още не е съвсем случайно.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. sql ПОРЪЧАЙТЕ ПО множество стойности в определен ред?

  2. Django+Postgres:текущата транзакция е прекратена, командите се игнорират до края на блока на транзакцията

  3. Как да разберем ОБЯСНИТЕЛЕН АНАЛИЗ

  4. Компресиране на текст в PostgreSQL

  5. Инсталирайте utf8 collation в PostgreSQL