SQL 데이터베이스의 단순 무작위 샘플


93

SQL에서 효율적인 단순 무작위 샘플을 어떻게 얻습니까? 문제의 데이터베이스는 MySQL을 실행하고 있습니다. 내 테이블은 최소 200,000 개의 행이고 약 10,000 개의 간단한 무작위 샘플을 원합니다.

"명백한"대답은 다음과 같습니다.

SELECT * FROM table ORDER BY RAND() LIMIT 10000

큰 테이블의 경우 너무 느립니다. RAND()모든 행 (이미 O (n)에 있음)을 호출 하고 정렬하여 기껏해야 O (n lg n)로 만듭니다. O (n)보다 빠르게 수행 할 수있는 방법이 있습니까?

참고 : Andrew Mao가 주석에서 지적했듯이 SQL Server에서이 방법을 사용하는 경우 NEWID()RAND () 가 모든 행에 대해 동일한 값을 반환 할 수 있으므로 T-SQL 함수를 사용해야합니다 .

편집 : 5 년 후

나는 더 큰 테이블 로이 문제에 다시 부딪 쳤고 두 가지 조정으로 @ignorant의 솔루션 버전을 사용하게되었습니다.

  • 원하는 샘플 크기의 2 ~ 5 배로 행을 샘플링하여 저렴하게 ORDER BY RAND()
  • RAND()삽입 / 업데이트 할 때마다 결과를 인덱싱 된 열에 저장합니다 . (데이터 세트가 업데이트가 많지 않은 경우이 열을 최신 상태로 유지하는 다른 방법을 찾아야 할 수 있습니다.)

테이블의 1000 개 항목 샘플을 가져 오기 위해 행 수를 세고 frozen_rand 열을 사용하여 평균 10,000 개 행으로 결과를 샘플링합니다.

SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high

  SELECT *
    FROM table
   WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000

(제 실제 구현에는 언더 샘플링을 방지하고 rand_high를 수동으로 래핑하는 데 더 많은 작업이 필요하지만 기본 아이디어는 "N을 몇 천으로 무작위로 줄이는 것"입니다.)

이로 인해 약간의 희생이 발생하지만 ORDER BY RAND()다시 충분히 작아 질 때까지 인덱스 스캔을 사용하여 데이터베이스를 샘플링 할 수 있습니다 .


3
RAND()후속 호출마다 동일한 값을 반환 하기 때문에 SQL 서버에서도 작동하지 않습니다 .
Andrew Mao

1
좋은 점-SQL Server 사용자는 대신 ORDER BY NEWID ()를 사용해야한다는 메모를 추가하겠습니다.
ojrac

모든 데이터를 정렬해야하기 때문에 여전히 매우 비효율적입니다. 일부 비율에 대한 무작위 샘플링 기술이 더 낫지 만 여기에 대한 많은 게시물을 읽은 후에도 충분히 무작위 인 허용 가능한 솔루션을 찾지 못했습니다.
앤드류 마오

질문을 읽으면 ORDER BY RAND ()가 O (n lg n)이기 때문에 구체적으로 묻는 것입니다.
ojrac

아래 muposat의 대답은 RAND ()의 통계적 무작위성에 너무 집착하지 않으면 훌륭합니다.
조쉬 그레이 퍼

답변:


25

여기에 이러한 유형의 문제에 대한 매우 흥미로운 논의가 있습니다. http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

나는 당신의 O (n lg n) 솔루션이 최고라는 테이블에 대한 가정이 전혀 없다고 생각합니다. 실제로 좋은 최적화 프로그램이나 약간 다른 기술을 사용하면 나열하는 쿼리가 조금 더 좋을 수 있습니다. O (m * n) 여기서 m은 전체 큰 배열을 정렬 할 필요가 없기 때문에 원하는 임의의 행 수입니다. , 가장 작은 m 번만 검색 할 수 있습니다. 그러나 당신이 게시 한 숫자의 경우 m은 어쨌든 lg n보다 큽니다.

우리가 시도 할 수있는 세 가지 가정 :

  1. 테이블에 고유 한 색인화 된 기본 키가 있습니다.

  2. 선택하려는 임의의 행 수 (m)가 테이블의 행 수 (n)보다 훨씬 적습니다.

  3. 고유 한 기본 키는 간격이없는 1에서 n까지의 정수입니다.

가정 1과 2 만 있으면 O (n)에서이 작업을 수행 할 수 있다고 생각하지만 가정 3과 일치하도록 테이블에 전체 인덱스를 작성해야하므로 반드시 빠른 O (n)이 아닙니다. 추가적으로 테이블에 대해 좋은 것을 가정 할 수 있다면 O (m log m)에서 작업을 수행 할 수 있습니다. 가정 3은 작업하기 쉽고 좋은 추가 속성입니다. 연속적으로 m 개의 숫자를 생성 할 때 중복을 보장하지 않는 멋진 난수 생성기를 사용하면 O (m) 솔루션이 가능합니다.

세 가지 가정이 주어지면 기본 아이디어는 1과 n 사이의 고유 한 난수 m 개를 생성 한 다음 테이블에서 해당 키가있는 행을 선택하는 것입니다. 나는 지금 내 앞에 mysql이나 아무것도 없기 때문에 약간의 의사 코드에서 이것은 다음과 같이 보일 것입니다.


create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)

-- generate m random keys between 1 and n
for i = 1 to m
  insert RandomKeysAttempt select rand()*n + 1

-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt

-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) < m
  NextAttempt = rand()*n + 1
  if not exists (select * from RandomKeys where RandomKey = NextAttempt)
    insert RandomKeys select NextAttempt

-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey

효율성에 대해 정말로 우려했다면 일종의 절차 언어로 임의 키 생성을 수행하고 결과를 데이터베이스에 삽입하는 것을 고려할 수 있습니다. SQL 이외의 거의 모든 것이 필요한 종류의 반복 및 난수 생성에서 더 낫기 때문입니다. .


임의의 키 선택에 고유 인덱스를 추가하고 삽입에서 중복을 무시하는 것이 좋습니다. 그러면 고유 한 항목을 제거 할 수 있고 조인이 더 빨라질 것입니다.
Sam Saffron

난 난수 알고리즘이 언급 한대로 UNIQUE 제약 조건을 사용하거나 2 * m 숫자를 생성하고 SELECT DISTINCT, ORDER BY id (선착순이므로 UNIQUE 제약 조건으로 축소)를 사용할 수 있다고 생각합니다. ) LIMIT m. 나는 그것을 좋아한다.
ojrac

임의 키 선택에 고유 인덱스를 추가 한 다음 삽입시 중복을 무시하는 것과 관련하여 정렬을 위해 O (m lg m) 대신 O (m ^ 2) 동작으로 돌아갈 수 있다고 생각했습니다. 한 번에 하나씩 임의의 행을 삽입 할 때 서버가 인덱스를 얼마나 효율적으로 유지하는지 확실하지 않습니다.
user12861

2 * m 숫자 등을 생성하기위한 제안에 관해서는 무슨 일이 있어도 작동하는 알고리즘을 원했습니다. 2 * m 난수에 m 개 이상의 중복이있을 가능성이 항상 있으므로 쿼리에 충분하지 않습니다.
user12861

1
테이블의 행 수를 어떻게 얻습니까?
Awesome-o

54

가장 빠른 해결책은

select * from table where rand() <= .3

이것이 제가 일을해야한다고 생각하는 이유입니다.

  • 각 행에 대해 난수를 생성합니다. 숫자는 0과 1 사이입니다.
  • 생성 된 숫자가 0에서 .3 (30 %) 사이 인 경우 해당 행을 표시할지 여부를 평가합니다.

이것은 rand ()가 균등 분포로 숫자를 생성한다고 가정합니다. 이를 수행하는 가장 빠른 방법입니다.

누군가가 그 해결책을 추천했고 증거없이 총에 맞았다는 것을 봤습니다.

  • 이것은 O (n)이지만 정렬이 필요하지 않으므로 O (n lg n)보다 빠릅니다.
  • mysql은 각 행에 대해 난수를 생성 할 수 있습니다. 이 시도 -

    INFORMATION_SCHEMA.TABLES 제한 10에서 rand ()를 선택합니다.

문제의 데이터베이스가 mySQL이므로 이것이 올바른 솔루션입니다.


1
첫째, 원하는 수에 가깝지만 정확한 원하는 수의 결과 대신 반드시 그 수에 가까운 반 무작위 수의 결과가 반환되기 때문에 이것이 실제로 질문에 답하지 않는다는 문제가 있습니다.
user12861 2013

1
다음으로 효율성에 관해서는 O (n)이며, 여기서 n은 테이블의 행 수입니다. 이는 O (m log m)만큼 좋지 않습니다. 여기서 m은 원하는 결과 수이고 m << n입니다. rand ()를 생성하고 상수와 비교하는 것이 매우 빠를 수 있기 때문에 실제로는 더 빠를 것이라고 여전히 옳을 수 있습니다. 알아 내려면 테스트해야합니다. 작은 테이블을 사용하면 이길 수 있습니다. 거대한 테이블과 훨씬 적은 수의 원하는 결과로 나는 그것을 의심합니다.
user12861 2013

1
@ user12861이 정확한 숫자를 얻지 못하는 것에 대해 옳지 만 데이터 세트를 적절한 대략적인 크기로 줄이는 좋은 방법입니다.
ojrac

1
데이터베이스는 다음 쿼리를 SELECT * FROM table ORDER BY RAND() LIMIT 10000 어떻게 처리합니까? 먼저 각 행에 대해 난수를 생성 한 다음 (내가 설명한 솔루션과 동일) 주문해야합니다. 정렬은 비용이 많이 듭니다! 이것이 내가 설명한 것보다 정렬이 필요하지 않기 때문에이 솔루션이 느린 이유입니다. 내가 설명한 솔루션에 제한을 추가 할 수 있으며 해당 행 수 이상을 제공하지 않습니다. 누군가가 올바르게 지적했듯이 정확한 샘플 크기를 제공하지는 않지만 무작위 샘플의 경우 EXACT는 대부분 엄격한 요구 사항이 아닙니다.
무지

최소 행 수를 지정하는 방법이 있습니까?
CMCDragonkai

5

분명히 일부 SQL 버전에는 TABLESAMPLE명령이 있지만 모든 SQL 구현 (특히 Redshift)에는 없습니다.

http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx


아주 멋지다! PostgreSQL 또는 MySQL / MariaDB에서도 구현되지 않은 것처럼 보이지만이를 지원하는 SQL 구현을 사용하는 경우 훌륭한 대답입니다.
ojrac

나는 그것이 TABLESAMPLE통계적 의미에서 무작위가 아니라는 것을 이해합니다 .
Sean

4

그냥 사용

WHERE RAND() < 0.1 

기록의 10 %를 얻거나

WHERE RAND() < 0.01 

기록의 1 % 등을 얻으려면


1
모든 행에 대해 RAND를 호출하여 O (n)으로 만듭니다. 포스터는 그보다 더 나은 것을 찾고있었습니다.
user12861

1
뿐만 아니라 RAND()후속 호출에 대해 동일한 값을 반환합니다 (적어도 MSSQL에서는). 즉, 전체 테이블을 얻거나 해당 확률로 테이블을 전혀 얻지 못할 것입니다.
Andrew Mao

4

ORDER BY RAND ()보다 빠름

이 방법이보다 훨씬 빠르도록 테스트 ORDER BY RAND()했으므로 O (n) 에서 실행됩니다. 시간에 매우 빠릅니다.

에서 http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx :

비 MSSQL 버전 -나는 이것을 테스트하지 않았습니다.

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()

MSSQL 버전 :

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)

레코드의 ~ 1 %를 선택합니다. 따라서 정확한 퍼센트 또는 레코드 수를 선택해야하는 경우 일부 안전 여유를 사용하여 백분율을 추정 한 다음 더 비싼 ORDER BY RAND()방법을 사용하여 결과 집합에서 초과 레코드를 무작위로 추출 합니다.

더 빠르게

잘 알려진 인덱싱 된 열 값 범위가 있기 때문에이 방법을 더욱 향상시킬 수있었습니다.

예를 들어, 균일하게 분포 된 정수 [0..max]가있는 인덱싱 된 열이있는 경우이를 사용하여 N 개의 작은 간격을 임의로 선택할 수 있습니다. 프로그램에서이 작업을 동적으로 수행하여 각 쿼리 실행에 대해 다른 집합을 가져옵니다. 이 하위 집합 선택은 O (N) 이며 전체 데이터 세트보다 훨씬 더 작을 수 있습니다.

내 테스트에서 ORDER BY RAND ()를 사용하여 3 분 에서 20 (20 mil) 샘플 레코드를 얻는 데 필요한 시간 을 0.0 초로 줄였습니다 !


1

나는 이러한 모든 솔루션이 대체없이 샘플링되는 것처럼 보인다는 점을 지적하고 싶습니다. 임의 정렬에서 상위 K 개 행을 선택하거나 임의 순서로 고유 한 키를 포함하는 테이블에 조인하면 대체없이 생성 된 임의 샘플이 생성됩니다.

샘플을 독립적으로 사용하려면 대체 샘플을 사용해야합니다. user12861의 솔루션과 유사한 방식으로 JOIN을 사용하여이를 수행하는 방법에 대한 한 가지 예는 질문 25451034 를 참조하십시오 . 이 솔루션은 T-SQL 용으로 작성되었지만 개념은 모든 SQL db에서 작동합니다.


0

집합을 기반으로 테이블의 ID (예 : 개수 5)를 검색 할 수 있다는 관찰부터 시작합니다.

select *
from table_name
where _id in (4, 1, 2, 5, 3)

우리는 문자열을 생성 할 수 있다면 "(4, 1, 2, 5, 3)"보다 효율적인 방법을 가질 수 있다는 결과를 얻을 수 있습니다 RAND().

예를 들어, Java에서 :

ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
    indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');

ID에 간격 indices이있는 경우 초기 배열 목록 은 ID에 대한 SQL 쿼리의 결과입니다.


0

정확히 m행 이 필요한 경우 현실적으로 SQL 외부에서 ID 하위 집합을 생성합니다. 대부분의 메소드는 어떤 시점에서 "n 번째"항목을 선택해야하며 SQL 테이블은 실제로는 배열이 아닙니다. 1과 개수 사이의 임의의 정수를 결합하기 위해 키가 연속적이라는 가정도 만족하기 어렵습니다. 예를 들어 MySQL은 기본적으로이를 지원하지 않으며 잠금 조건이 까다 롭습니다. .

다음 은 일반 BTREE 키를 가정 한 O(max(n, m lg n))-time, O(n)-space 솔루션입니다.

  1. 데이터 테이블의 키 열의 모든 값을 순서에 관계없이 원하는 스크립팅 언어의 배열로 가져옵니다. O(n)
  2. Fisher-Yates 셔플을 수행하고 m스왑 후 중지 하고에서 하위 배열 [0:m-1]을 추출합니다 .ϴ(m)
  3. 원래 데이터 세트 (예 :)와 하위 배열을 "결합"합니다 SELECT ... WHERE id IN (<subarray>).O(m lg n)

SQL 외부에서 임의의 하위 집합을 생성하는 모든 메서드는 최소한이 복잡성이 있어야합니다. 더 빨리 초과 할 수 없습니다 조인 O(m lg n)(때문에 BTREE와 O(m)주장은 대부분의 엔진에 대한 환상이다)와 셔플은 아래 묶여있다 nm lg n및 점근 동작에 영향을주지 않습니다.

Pythonic 의사 코드에서 :

ids = sql.query('SELECT id FROM t')
for i in range(m):
  r = int(random() * (len(ids) - i))
  ids[i], ids[i + r] = ids[i + r], ids[i]

results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])

0

Netezza에서 3000 개의 임의 레코드 선택 :

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000

SQL 방언 관련 메모를 추가하는 것 외에는 'ORDER BY rand () LIMIT $ 1'없이 행의 임의 샘플을 쿼리하는 방법에 대한 질문에 대답하지 않는다고 생각합니다.
ojrac

0

시험

SELECT TOP 10000 * FROM table ORDER BY NEWID()

너무 복잡하지 않고 원하는 결과를 얻을 수 있습니까?


참고 NEWID()T-SQL에 따라 다릅니다.
Peter O.

죄송합니다. 그것은. 감사합니다. 내가 더 나은 방법으로했던 것처럼 여기에 오는 사람이 있는지, T-SQL을 사용하고 있는지 아는 것이 유용합니다
Northernlad

ORDER BY NEWID()기능적으로 동일합니다 ORDER BY RAND()- RAND()집합의 모든 행을 호출 합니다-O (n)-그런 다음 전체 항목을 정렬합니다-O (n lg n). 즉,이 질문이 개선하고자하는 최악의 솔루션입니다.
ojrac

0

Microsoft SQL Server, PostgreSQL 및 Oracle (MySQL 또는 SQLite 제외)과 같은 특정 방언에서는 다음과 같은 작업을 수행 할 수 있습니다.

select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);

를 사용하지 (10000 rows)않고 수행 하지 않는 이유 topTABLESAMPLE논리가 매우 부정확 한 행 수를 제공하므로 (예 : 75 %, 때로는 1.25 %) 원하는 정확한 수를 오버 샘플링하고 선택하기 때문입니다. 는 REPEATABLE (123)임의의 씨앗을 제공하기위한 것입니다.


-4

아마도 당신은 할 수 있습니다

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)

1
내 데이터의 임의의 조각을 선택하는 것처럼 보입니다. 좀 더 복잡한 것을 찾고 있습니다. 10,000 개의 무작위로 분산 된 행입니다.
ojrac

그런 다음 데이터베이스에서 수행하려는 경우 유일한 옵션은 ORDER BY rand ()입니다.
staticsan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.