Postgres에서 빠른 무작위 행 선택


98

수백만 개의 행을 포함하는 postgres에 테이블이 있습니다. 인터넷에서 확인한 결과 다음을 발견했습니다.

SELECT myid FROM mytable ORDER BY RANDOM() LIMIT 1;

작동하지만 정말 느립니다 ... 그 쿼리를 만드는 다른 방법이 있습니까, 아니면 모든 테이블을 읽지 않고 임의의 행을 선택하는 직접적인 방법이 있습니까? 그런데 'myid'는 정수이지만 빈 필드 일 수 있습니다.


1
임의의 행을 여러 개 선택하려면 다음 질문을 참조하십시오. stackoverflow.com/q/8674718/247696
Flimm

답변:


99

OFFSET에서 와 같이 실험 해 볼 수 있습니다 .

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

N행의 수입니다 mytable. SELECT COUNT(*)의 값을 파악 하려면 먼저 a 를 수행해야 할 수 있습니다 N.

업데이트 (Antony Hatchkins 제공)

floor여기에서 사용해야 합니다.

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 행의 테이블을 고려하십시오. random()*N생성 0 <= x < 2예를 들어 SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;있기 때문에 가까운 INT로 암시 적 라운딩의 0 행을 반환합니다.


SELECT COUNT(*)? 보다 작은 N을 사용하는 것이 합리적 입니다. 테이블의 모든 값을 사용하는 것이 아니라 일부만 사용합니까?
Juan

@Juan 귀하의 요구 사항에 따라 다릅니다.
NPE 2011 년

EXPLAIN SELECT ...N의 다른 값과 함께 사용하면 쿼리에 동일한 비용이 발생합니다. 그러면 N의 최대 값을 찾는 것이 더 낫다고 생각합니다.
Juan

3
내 대답은 아래의 버그 수정 참조
안토니 Hatchkins

2
하나의 오류가 있습니다. 첫 번째 행을 반환하지 않으며 마지막 행 다음 행을 반환하려고 시도하기 때문에 오류 1 / COUNT (*)를 생성합니다.
Ian

62

PostgreSQL 9.5는 훨씬 더 빠른 샘플 선택을위한 새로운 접근 방식을 도입했습니다 : TABLESAMPLE

구문은

SELECT * FROM my_table TABLESAMPLE BERNOULLI(percentage);
SELECT * FROM my_table TABLESAMPLE SYSTEM(percentage);

정확한 백분율을 계산하려면 테이블의 COUNT를 알아야하기 때문에 하나의 행만 선택하려는 경우 최적의 솔루션이 아닙니다.

느린 COUNT를 방지하고 1 행에서 수십억 행까지의 테이블에 대해 빠른 TABLESAMPLE을 사용하려면 다음을 수행 할 수 있습니다.

 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.000001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.00001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.0001) LIMIT 1;
 -- if you got no result:
 SELECT * FROM my_table TABLESAMPLE SYSTEM(0.001) LIMIT 1;
 ...

이것은 그렇게 우아하게 보이지 않을 수도 있지만 아마도 다른 답변보다 빠를 것입니다.

BERNULLI 또는 SYSTEM을 사용할 것인지 결정하려면 http://blog.2ndquadrant.com/tablesample-in-postgresql-9-5-2/ 에서 차이점에 대해 읽어보십시오.


2
이것은 다른 어떤 답변보다 훨씬 빠르고 쉽습니다.
Hayden Schiff

1
카운트를 얻기 위해 하위 쿼리를 사용할 수없는 이유는 무엇입니까? SELECT * FROM my_table TABLESAMPLE SYSTEM(SELECT 1/COUNT(*) FROM my_table) LIMIT 1;?
machineghost

2
@machineghost "느린 COUNT를 피하려면 ..."... 데이터가 너무 작아서 합리적인 시간에 셀 수 있다면 시도해보십시오! :-)
alfonx

2
@machineghost SELECT reltuples FROM pg_class WHERE relname = 'my_table'개수 추정에 사용 합니다.
Hynek -Pichi- Vychodil

@ Hynek-Pichi-Vychodil 아주 좋은 입력! 추정치가 구식이 아닌지 확인하려면 최근에 VACUUM ANALYZE를해야합니다.하지만 어쨌든 좋은 데이터베이스는 적절하게 분석되어야합니다 .. 그리고 그것은 모두 특정 사용 사례에 달려 있습니다. 보통 거대한 테이블은 그렇게 빨리 자라지 않습니다 ... 감사합니다!
alfonx

34

나는 이것을 하위 쿼리로 시도했고 잘 작동했습니다. 오프셋, 적어도 Postgresql v8.4.4에서는 정상적으로 작동합니다.

select * from mytable offset random() * (select count(*) from mytable) limit 1 ;

실제로 v8.4는 이것이 작동하는 데 필수적이며 <= 8.3에서는 작동하지 않습니다.
Antony Hatchkins

1
내 대답은 아래의 버그 수정 참조
안토니 Hatchkins

32

다음을 사용해야합니다 floor.

SELECT myid FROM mytable OFFSET floor(random()*N) LIMIT 1;

2 개 행의 테이블을 고려하십시오. random()*N0 <= x <2를 생성하고 예를 들어 SELECT myid FROM mytable OFFSET 1.7 LIMIT 1;가장 가까운 int로 내재적으로 반올림하기 때문에 0 행을 반환합니다.
안토니 Hatchkins

안타깝게도 더 높은 LIMIT를 사용하려는 경우에는 작동하지 않습니다. 3 개의 항목을 가져와야하므로 ORDER BY RANDOM () 구문을 사용해야합니다.
Alexis Wilke 2012

1
세 개의 연속 쿼리는 여전히 하나보다 빠르며 order by random(), 대략 3*O(N) < O(NlogN)실제 수치는 인덱스로 인해 약간 다를 수 있습니다.
Antony Hatchkins 2012

내 문제는 3 개 항목을 구분하고있을 필요가있다 WHERE myid NOT IN (1st-myid)WHERE myid NOT IN (1st-myid, 2nd-myid)결정은 오프셋에 의해 이루어지기 때문에 작업을 것 없습니다. 음 ... 두 번째와 세 번째 SELECT에서 N을 1과 2로 줄일 수 있다고 생각합니다.
Alexis Wilke 2012

귀하 또는 누구든지 내가 사용해야하는 이유에 대한 답변으로이 답변을 확장 할 수 floor()있습니까? 어떤 이점이 있습니까?
ADTC

14

이 링크에서 몇 가지 다른 옵션을 확인하십시오. http://www.depesz.com/index.php/2007/09/16/my-thoughts-on-getting-random-row/

최신 정보: (A. 해치 킨스)

(매우) 긴 기사의 요약은 다음과 같습니다.

저자는 네 가지 접근 방식을 나열합니다.

1) ORDER BY random() LIMIT 1; -느림

2) ORDER BY id where id>=random()*N LIMIT 1-간격이있는 경우 불균일

3) 임의의 열-가끔 업데이트해야 함

4) 사용자 정의 임의 집계 -교활한 방법, 느릴 수 있음 : random ()을 N 번 생성해야 함

방법 # 2를 사용하여 개선 할 것을 제안합니다.

5) ORDER BY id where id=random()*N LIMIT 1 결과가 비어있는 경우 후속 재 쿼리.


왜 그들이 오프셋을 커버하지 않았는지 궁금합니다. ORDER를 사용하는 것은 임의의 행을 얻기 위해 의문의 여지가 없습니다. 다행히도 OFFSET은 답변에서 잘 다루어집니다.
androidguy

4

임의의 행을 가져 오는 가장 쉽고 빠른 방법은 tsm_system_rows확장 을 사용하는 것 입니다.

CREATE EXTENSION IF NOT EXISTS tsm_system_rows;

그런 다음 원하는 정확한 행 수를 선택할 수 있습니다.

SELECT myid  FROM mytable TABLESAMPLE SYSTEM_ROWS(1);

PostgreSQL 9.5 이상에서 사용할 수 있습니다.

참조 : https://www.postgresql.org/docs/current/static/tsm-system-rows.html


1
공정한 경고, 이것은 완전히 무작위가 아닙니다. 작은 테이블에서는 항상 첫 번째 행을 순서대로 반환했습니다.
Ben Aubin

1
예 이것은 문서 (위 링크)에 명확하게 설명되어 있습니다.«내장 된 SYSTEM 샘플링 방법과 마찬가지로 SYSTEM_ROWS는 블록 수준 샘플링을 수행하므로 샘플이 완전히 무작위가 아니지만 클러스터링 효과를받을 수 있습니다. 행 수를 요청합니다. ». 작은 데이터 세트가있는 경우는 ORDER BY random() LIMIT 1;충분히 빠릅니다.
daamien

나는 것을보고. 링크를 클릭하지 않는 사람이나 링크가 향후에 죽는다면 누구에게나 분명히 알리고 싶었습니다.
Ben Aubin

1
또한 이것은 쿼리를 실행 한 다음 무작위로 하나 또는 일부 레코드를 선택하는 것과 반대로 / 비교하여 테이블에서 임의의 행을 선택하고 THEN 필터링하는 경우에만 작동합니다.
nomen

3

.NET없이 매우 빠른 솔루션을 찾았습니다 TABLESAMPLE. 보다 훨씬 빠릅니다 OFFSET random()*N LIMIT 1. 테이블 카운트도 필요하지 않습니다.

아이디어는 무작위이지만 예측 가능한 데이터로 표현식 인덱스를 만드는 것입니다 md5(primary key).

다음은 1M 행 샘플 데이터를 사용한 테스트입니다.

create table randtest (id serial primary key, data int not null);

insert into randtest (data) select (random()*1000000)::int from generate_series(1,1000000);

create index randtest_md5_id_idx on randtest (md5(id::text));

explain analyze
select * from randtest where md5(id::text)>md5(random()::text)
order by md5(id::text) limit 1;

결과:

 Limit  (cost=0.42..0.68 rows=1 width=8) (actual time=6.219..6.220 rows=1 loops=1)
   ->  Index Scan using randtest_md5_id_idx on randtest  (cost=0.42..84040.42 rows=333333 width=8) (actual time=6.217..6.217 rows=1 loops=1)
         Filter: (md5((id)::text) > md5((random())::text))
         Rows Removed by Filter: 1831
 Total runtime: 6.245 ms

이 쿼리는 때때로 (약 1 / Number_of_rows 확률로) 0 행을 반환 할 수 있으므로 확인하고 다시 실행해야합니다. 또한 확률은 정확히 동일하지 않습니다. 일부 행은 다른 행보다 확률이 높습니다.

비교하려고:

explain analyze SELECT id FROM randtest OFFSET random()*1000000 LIMIT 1;

결과는 매우 다양하지만 매우 나쁠 수 있습니다.

 Limit  (cost=1442.50..1442.51 rows=1 width=4) (actual time=179.183..179.184 rows=1 loops=1)
   ->  Seq Scan on randtest  (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.016..134.835 rows=915702 loops=1)
 Total runtime: 179.211 ms
(3 rows)

2
예, 빠릅니다. 정말 무작위입니다. 다른 기존 값 다음으로 큰 값이되는 md5 값은 선택 될 확률이 매우 낮고 숫자 공간에서 큰 간격 이후의 값은 훨씬 더 큰 기회를 갖습니다 (사이에있는 가능한 값의 수만큼 더 큼) . 결과 분포는 무작위가 아닙니다.
Erwin Brandstetter 2015 년

매우 흥미 롭습니다. 복권과 같은 쿼리의 사용 사례에서 작동 할 수 있습니까? 쿼리는 사용 가능한 모든 티켓을 조사하고 무작위로 하나의 단일 티켓 만 반환해야합니다. 또한 비관적 잠금 (업데이트를 위해 선택)을 사용할 수 있습니까?
Mathieu

복권과 관련된 모든 경우에는 실제로 공정하고 암호 학적으로 안전한 무작위 샘플링을 사용해야합니다. 예를 들어 기존 ID를 찾을 때까지 1과 max (id) 사이의 임의의 숫자를 선택합니다. 이 답변의 방법은 공정하거나 안전하지 않습니다. 빠릅니다. '어떤 것을 테스트하기 위해 행의 임의 1 % 가져 오기'또는 '무작위 5 개 항목 표시'와 같은 작업에 사용할 수 있습니다.
Tometzky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.