임의의 행을 선택하는 가장 좋은 방법 PostgreSQL


345

PostgreSQL에서 무작위로 행을 선택하고 싶습니다.

select * from table where random() < 0.01;

그러나 다른 사람들은 이것을 추천합니다 :

select * from table order by random() limit 1000;

나는 500 백만 행이있는 매우 큰 테이블을 가지고 있습니다.

어떤 접근법이 더 낫습니까? 차이점은 무엇입니까? 임의의 행을 선택하는 가장 좋은 방법은 무엇입니까?


1
안녕 잭, 귀하의 응답에 의해 실행 시간이 순서대로 느리지 만, 어떤 것이 다른지 알고 싶습니다 ...
nanounanue

어 .. 천만에요 다른 접근법을 벤치마킹 해 보셨습니까?

도 있습니다 훨씬 빠른 방법. 그것은 모두 요구 사항과 작업해야 할 내용에 따라 다릅니다. 정확히 1000 행이 필요합니까? 테이블에 숫자 ID가 있습니까? 갭이 없거나 적거나 많은 차이가 있습니까? 속도는 얼마나 중요합니까? 시간 단위당 요청 수는 몇 개입니까? 모든 요청에 ​​다른 세트가 필요합니까? 또는 정의 된 시간 조각에 대해 동일 할 수 있습니까?
Erwin Brandstetter

6
첫 번째 옵션 "(random () <0.01)"은 임의의 숫자가 0.01 미만인 경우 응답이 행을 얻을 수 없으므로 수학적으로 올바르지 않습니다. 또는 임계 값 이상. 두 번째 옵션은 항상 옳습니다
Herme

1
하나의 행만 선택하려면이 질문을 참조하십시오. stackoverflow.com/q/5297396/247696
Flimm

답변:


230

귀하의 사양 (주석에 추가 정보 포함)이 주어지면,

  • 공백이 거의없는 숫자 ID 열 (정수)이 있습니다.
  • 쓰기 작업이 거의 없거나 거의 없습니다.
  • ID 열을 색인해야합니다! 기본 키가 훌륭하게 제공됩니다.

아래 쿼리에는 큰 테이블의 순차적 스캔이 필요하지 않고 인덱스 스캔 만 필요합니다.

먼저 기본 쿼리에 대한 추정치를 가져옵니다.

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은 우연히 여러 번 선택할 수 있으므로 (ID 공간이 크지는 않지만) 생성 된 숫자를 그룹화하십시오 (또는 use DISTINCT).

  • ids를 큰 테이블에 결합하십시오 . 인덱스가 있으면 매우 빠릅니다.

  • 마지막으로 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
   )
SELECT *
FROM   random_pick
LIMIT  1000;  -- actual limit

기본 쿼리에서 더 작은 잉여 로 작업 할 수 있습니다. 간격이 너무 많아서 첫 번째 반복에서 충분한 행을 찾지 못하면 rCTE는 반복적 인 용어로 계속 반복됩니다. 여전히 ID 공간에 상대적으로 적은 간격이 필요 하거나 한계에 도달하기 전에 재귀가 건조하게 실행될 수 있습니다.

UNIONrCTE에서 중복이 제거됩니다 .

외부 LIMIT는 행이 충분 해지면 CTE를 중지시킵니다.

이 쿼리는 사용 가능한 인덱스를 사용하고 실제로 임의의 행을 생성하며 제한이 충족 될 때까지 멈추지 않습니다 (재귀가 건조하지 않는 한). 다시 쓰려고하면 여기에 여러 가지 함정이 있습니다.

기능으로 포장

다양한 파라미터로 반복 사용하는 경우 :

CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
  RETURNS SETOF big 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
   )
   SELECT *
   FROM   random_pick
   LIMIT  _limit;
END
$func$  LANGUAGE plpgsql VOLATILE ROWS 1000;

요구:

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

이 표를 모든 표에서 작동하도록 만들 수도 있습니다. PK 열과 표의 이름을 다형성 유형으로 사용하고 사용 EXECUTE하십시오. 보다:

가능한 대안

요구 사항 이 반복 통화에 대해 동일한 세트를 허용하는 경우 (그리고 반복 통화에 대해 이야기하는 경우) 구체화 된 관점을 고려합니다 . 위의 쿼리를 한 번 실행하고 결과를 테이블에 씁니다. 사용자는 가벼운 속도로 준 무작위 선택을합니다. 선택한 간격 또는 이벤트마다 무작위 선택을 새로 고칩니다.

Postgres 9.5 소개 TABLESAMPLE SYSTEM (n)

n백분율은 어디에 있습니까 ? 매뉴얼 :

BERNOULLISYSTEM샘플링 방법은 각각 다음과 같이 표현 샘플 테이블의 일부이며, 하나의 인자 수용 0과 100 사이의 비율 . 이 인수는 모든 real값을 갖는 표현식 일 수 있습니다 .

대담한 강조 광산. 그건 매우 빠르게 ,하지만 결과는 정확히 무작위 없습니다 . 매뉴얼 다시 :

SYSTEM방법은 BERNOULLI작은 샘플링 백분율이 지정된 경우 방법 보다 훨씬 빠르지 만 군집화 효과의 결과로 테이블의 덜 무작위 샘플을 반환 할 수 있습니다.

리턴되는 행 수는 크게 다를 수 있습니다. 예를 들어, 대략 1000 행 을 얻으려면 :

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

관련 :

또는 추가 모듈 tsm_system_rows 를 설치하여 요청 된 행 수를 정확하게 (충분한 경우) 가져오고보다 편리한 구문을 허용하십시오.

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

자세한 내용은 Evan의 답변 을 참조하십시오.

그러나 그것은 여전히 ​​정확히 무작위가 아닙니다.


t 테이블 은 어디에 정의되어 있습니까 ? t 대신에 r 해야합니까 ?
Luc M

1
@LucM : 정의된다 : JOIN bigtbl t, 짧은이다 JOIN bigtbl AS t. t테이블 별칭 입니다 bigtbl. 그 목적은 구문을 단축하는 것이지만이 특별한 경우에는 필요하지 않습니다. 내 대답에서 쿼리를 단순화하고 간단한 버전을 추가했습니다.
Erwin Brandstetter

generate_series (1,1100)의 값 범위의 목적은 무엇입니까?
Awesome-o

@ Awesome-o : 목표는 1000 행을 검색하는 것입니다. 몇 가지 격차 또는 (아마도 가능하지만 가능한) 중복 난수를 보완하기 위해 추가 10 %로 시작합니다 ... 설명은 내 대답에 있습니다.
Erwin Brandstetter

Erwin, 귀하의 "가능한 대안": stackoverflow.com/a/23634212/430128 의 변형을 게시했습니다 . 당신의 생각에 관심이 있으십시오.
라만

100

다음을 사용하여 두 실행 계획을 검토하고 비교할 수 있습니다.

EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;

큰 테이블 1 에 대한 빠른 테스트 는 ORDER BY첫 번째 테이블이 전체 테이블을 정렬 한 다음 처음 1000 개의 항목을 선택 함을 보여줍니다 . 큰 테이블을 정렬하면 해당 테이블을 읽을뿐만 아니라 임시 파일을 읽고 쓰는 작업도 포함됩니다. 는 where random() < 0.1한 번만 전체 테이블을 스캔합니다.

큰 테이블의 경우 한 번의 전체 테이블 스캔으로 시간이 오래 걸릴 수 있으므로 원하는 것이 아닐 수도 있습니다.

세 번째 제안은

select * from table where random() < 0.01 limit 1000;

이것은 1000 개의 행을 찾 자마자 테이블 스캔을 중지하고 더 빨리 리턴합니다. 물론 이것은 임의성을 조금 떨어 뜨립니다. 그러나 아마도 이것은 귀하의 경우에는 충분합니다.

편집 : 이 고려 사항 외에도 이미 질문 한 내용을 확인할 수 있습니다. 쿼리를 사용하면 [postgresql] random꽤 많은 히트가 반환됩니다.

그리고 몇 가지 더 많은 접근법을 간략히 설명하는 링크 된 depez 기사 :


1 "전체 테이블이 메모리에 맞지 않습니다"와 같이 "큰"것입니다.


1
주문을 위해 임시 파일을 작성하는 것이 좋습니다. 정말 큰 타격입니다. 우리가 할 수 random() < 0.02있고 그 목록을 섞은 다음에 limit 1000! 정렬은 수천 행 (롤)에서 덜 비쌉니다.
Donald Miner

"select * from random () <0.05 limit 500;" postgresql을위한 더 쉬운 방법 중 하나입니다. 처리를 위해 한 번에 500 % 이하의 결과를 5 % 선택해야하는 프로젝트 중 하나에서이 기능을 사용했습니다.
tgharold

세계에서 500m 행 테이블에서 샘플을 검색하기 위해 O (n) 전체 스캔을 고려한 이유는 무엇입니까? 큰 테이블에서는 엄청나게 느리고 완전히 불필요합니다.
mafu

76

random ()에 의한 postgresql 순서, 무작위 순서로 행을 선택하십시오.

select your_columns from your_table ORDER BY random()

random () 별개의 postgresql 순서 :

select * from 
  (select distinct your_columns from your_table) table_alias
ORDER BY random()

임의의 한 행으로 postgresql 순서 :

select your_columns from your_table ORDER BY random() limit 1

1
select your_columns from your_table ORDER BY random() limit 145mil 행에서 실행하는 데 약 2 분 소요
nguyên

이 속도를 높일 수있는 방법이 있습니까?
CpILL

43

PostgreSQL 9.5부터는 테이블에서 임의의 요소를 가져 오는 새로운 구문이 있습니다.

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);

이 예제는의 요소 중 5 %를 제공합니다 mytable.

이 블로그 게시물에 대한 자세한 설명을 참조하십시오 : http://www.postgresql.org/docs/current/static/sql-select.html


3
문서에서 중요한 참고 사항 : "SYSTEM 방법은 지정된 확률로 각 블록을 선택하여 블록 레벨 샘플링을 수행합니다. 선택한 각 블록의 모든 행이 반환됩니다. SYSTEM 샘플링 방법은 샘플링 비율이 작은 경우 BERNOULLI 방법보다 훨씬 빠릅니다. "이 지정 되었으나 군집 효과의 결과로 테이블의 덜 무작위 샘플을 반환 할 수 있습니다."
Tim

1
백분율 대신 여러 행을 지정하는 방법이 있습니까?
Flimm

4
TABLESAMPLE SYSTEM_ROWS(400)400 개의 임의 행 샘플을 얻는 데 사용할 수 있습니다 . 이 명령문 을 사용하려면 내장 tsm_system_rows확장 을 활성화해야 합니다.
Mickaël Le Baillif

27

ORDER BY가있는 것이 느릴 것입니다.

select * from table where random() < 0.01;레코드별로 기록하고 임의로 필터링할지 여부를 결정합니다. O(N)각 레코드를 한 번만 확인 하면 되기 때문입니다.

select * from table order by random() limit 1000;전체 테이블을 정렬 한 다음 처음 1000 개를 선택합니다. 장면 뒤의 부두 마법을 제외하고 순서는입니다 O(N * log N).

random() < 0.01하나 의 단점 은 다양한 수의 출력 레코드를 얻을 수 있다는 것입니다.


무작위로 정렬하는 것보다 일련의 데이터를 섞는 더 좋은 방법 이 있습니다. Fisher-Yates Shuffle 은에서 실행됩니다 O(N). 그러나 SQL에서 셔플을 구현하는 것은 상당히 어려운 일입니다.


3
첫 번째 예제의 끝에 Limit 1을 추가 할 수없는 이유는 없습니다. 유일한 문제는 레코드를 다시 얻지 못할 가능성이 있다는 것입니다. 따라서 코드에서이를 고려해야합니다.
Relequestual

Fisher-Yates의 문제점은 전체 데이터 세트를 선택하려면 메모리에 전체 데이터 세트가 있어야한다는 것입니다. 매우 큰 데이터 세트에는 적합하지 않음 :(
CpILL

16

여기 나를위한 결정이 있습니다. 이해하고 실행하는 것이 매우 간단합니다.

SELECT 
  field_1, 
  field_2, 
  field_2, 
  random() as ordering
FROM 
  big_table
WHERE 
  some_conditions
ORDER BY
  ordering 
LIMIT 1000;

6
이 솔루션이 ORDER BY random()작동하지만 큰 테이블로 작업 할 때 효율적이지 않을 수 있다고 생각 합니다.
Anh Cao

15
select * from table order by random() limit 1000;

원하는 행 수를 알고 있으면를 확인하십시오 tsm_system_rows.

tsm_system_rows

모듈은 SELECT 명령의 TABLESAMPLE 절에서 사용할 수있는 테이블 샘플링 방법 SYSTEM_ROWS를 제공합니다.

이 테이블 샘플링 방법은 읽을 최대 행 수인 단일 정수 인수를 허용합니다. 테이블에 충분한 행이 포함되어 있지 않으면 전체 테이블이 선택되지 않는 한 결과 샘플에는 항상 정확히 많은 행이 포함됩니다. 내장 된 SYSTEM 샘플링 방법과 마찬가지로 SYSTEM_ROWS는 블록 수준 샘플링을 수행하므로 샘플이 완전히 임의적이지는 않지만 특히 적은 수의 행만 요청하는 경우 클러스터링 효과가 발생할 수 있습니다.

먼저 확장을 설치하십시오

CREATE EXTENSION tsm_system_rows;

그런 다음 쿼리

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);

2
추가 된 답변에 대한 링크를 추가했습니다 SYSTEM. 내장 방법 보다 눈에 띄는 개선 입니다.
Erwin Brandstetter 1

난 그냥 질문에 대답했다 여기에 내가 상당한 수행하는 동안 (임의의 단일 레코드) 벤치마킹 및 테스트tsm_system_rowstsm_system_time확장. 최대한 멀리 볼 수, 그들은 아무것도하지만, 절대적으로 사실상 쓸모가 최소한의 임의 행보세요. 당신이 내 분석의 타당성에 대해 신속하게보고 의견을 제시 할 수 있다면 감사 할 것입니다.
Vérace

6

하나의 행만 원하면 offset에서 파생 된 계산을 사용할 수 있습니다 count.

select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));

2

Erwin Brandstetter에 의해 요약 된 구체화 된 관점 "가능한 대안"의 변형 이 가능하다.

예를 들어, 리턴 된 무작위 값에서 중복을 원하지 않는다고 가정하십시오. 따라서 (랜덤 화되지 않은) 값 세트를 포함하는 기본 테이블에서 부울 값을 설정해야합니다.

이것이 입력 테이블이라고 가정합니다.

id_values  id  |   used
           ----+--------
           1   |   FALSE
           2   |   FALSE
           3   |   FALSE
           4   |   FALSE
           5   |   FALSE
           ...

ID_VALUES필요에 따라 테이블을 채우십시오 . 그런 다음 Erwin에서 설명한대로 ID_VALUES테이블을 한 번 무작위 화하는 구체화 된보기를 작성하십시오 .

CREATE MATERIALIZED VIEW id_values_randomized AS
  SELECT id
  FROM id_values
  ORDER BY random();

구체화 된보기에는 사용 된 열이 포함되어 있지 않습니다.이 열은 빠르게 오래된 정보가되기 때문입니다. 뷰에는 id_values테이블에 있을 수있는 다른 열이 포함될 필요도 없습니다 .

임의의 값을 얻으려면 (및 "소비") UPDATE-RETURNING on 을 사용하고 조인으로 id_values선택 하고 원하는 기준을 적용하여 관련 가능성 만 얻으십시오. 예를 들면 다음과 같습니다.id_valuesid_values_randomized

UPDATE id_values
SET used = TRUE
WHERE id_values.id IN 
  (SELECT i.id
    FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id
    WHERE (NOT i.used)
    LIMIT 5)
RETURNING id;

변경 LIMIT필요에 따라 - 당신이 한 번에 하나 개의 임의의 값 변경이 필요한 경우 LIMIT에를 1.

적절한 인덱스를 사용하면 id_valuesUPDATE-RETURNING이 적은로드로 매우 빠르게 실행되어야한다고 생각합니다. 하나의 데이터베이스 왕복으로 무작위 값을 리턴합니다. "적격"행에 대한 기준은 필요한만큼 복잡 할 수 있습니다. 새 행은 id_values언제든지 테이블에 추가 할 수 있으며 구체화 된보기를 새로 고치면 (피크가 적은 시간에 실행될 수 있음) 애플리케이션에 액세스 할 수 있습니다. 구체화 된 뷰의 작성 및 새로 고침은 느리지 만 새 ID를 id_values테이블에 추가 할 때만 실행하면됩니다 .


매우 흥미로운. pg_try_advisory_xact_lock으로 업데이트하려면 select ..를 사용하여 업데이트해야 할뿐만 아니라 select .. (즉, 많은 동시 읽기 및 쓰기가 필요함)
Mathieu

1

내 경험에서 얻은 한 가지 교훈 :

offset floor(random() * N) limit 1보다 빠르지 않습니다 order by random() limit 1.

offsetPostgres에서 정렬 시간을 절약해야하기 때문에 접근법이 더 빠를 것이라고 생각했습니다 . 그렇지 않은 것으로 밝혀졌습니다.


0

rtype 이라는 열을 추가하십시오 serial. 색인 r.

200,000 개의 행이 있다고 가정하면 n0 n<<= 200, 000 인 난수를 생성 할 것 입니다.

로 행을 선택하고 r > n정렬 ASC한 후 가장 작은 행을 선택하십시오 .

암호:

select * from YOUR_TABLE 
where r > (
    select (
        select reltuples::bigint AS estimate
        from   pg_class
        where  oid = 'public.YOUR_TABLE'::regclass) * random()
    )
order by r asc limit(1);

코드는 자명하다. 가운데의 하위 쿼리는 https://stackoverflow.com/a/7945274/1271094 에서 테이블 행 수를 빠르게 추정하는 데 사용됩니다 .

응용 프로그램 레벨에서 n> 행 수> 여러 행을 선택해야하는 경우 명령문을 다시 실행 해야합니다.


나는 짧고 우아하기 때문에 이것을 좋아합니다 :) 그리고 심지어 그것을 향상시킬 수있는 방법을 찾았습니다.
fxtentacle

select * from YOUR_TABLE 여기서 r> (select (oid_'public.YOUR_TABLE ':: regclass) pg_class에서 reltuples :: bigint AS 추정값 선택 * random ()) :: rIG에 의한 BIGINT 순서 limit (1);
fxtentacle

0

나는 파티에 조금 늦었다는 것을 알고 있지만 pg_sample 이라는 멋진 도구를 발견했습니다 .

pg_sample -참조 무결성을 유지하면서 더 큰 PostgreSQL 데이터베이스에서 작은 샘플 데이터 세트를 추출합니다.

나는 350M 행 데이터베이스로 이것을 시도했고 정말 빠르다 . 임의 에 대해 모른다 .

./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.