[mongodb] MongoDB의 무작위 레코드

거대한 (1 억 레코드)에서 무작위 레코드를 얻으려고합니다 mongodb.

가장 빠르고 효율적인 방법은 무엇입니까? 데이터가 이미 있으며 임의의 숫자를 생성하고 임의의 행을 얻을 수있는 필드가 없습니다.

어떤 제안?



답변

MongoDB 3.2 릴리스부터는 $sample집계 파이프 라인 연산자를 사용하여 콜렉션에서 N 개의 임의 문서를 얻을 수 있습니다 .

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

필터링 된 컬렉션의 하위 집합에서 임의의 문서를 선택 $match하려면 파이프 라인 앞에 스테이지를 추가하십시오 .

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

주석에서 언급했듯이 size1보다 크면 반환 된 문서 샘플에 중복이있을 수 있습니다.


답변

모든 레코드 수를 계산하고 0과 수 사이의 난수를 생성 한 후 다음을 수행하십시오.

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()


답변

MongoDB 3.2 업데이트

3.2 소개 된 $ sample 집계 파이프 라인에 을 .

좋은 것도 있습니다 블로그 게시물도 있습니다실습에 관한 .

이전 버전의 경우 (이전 답변)

이것은 실제로 기능 요청이었습니다. http://jira.mongodb.org/browse/SERVER-533 “Wo n’t fix”아래에 제출되었습니다.

요리 책에는 컬렉션에서 임의의 문서를 선택하기위한 매우 좋은 레시피가 있습니다. http://cookbook.mongodb.org/patterns/random-attribute/

레시피를 역설하려면 문서에 임의의 숫자를 할당하십시오.

db.docs.save( { key : 1, ..., random : Math.random() } )

그런 다음 임의의 문서를 선택하십시오.

rand = Math.random()
result = db.docs.findOne( { key : 2, random : { $gte : rand } } )
if ( result == null ) {
  result = db.docs.findOne( { key : 2, random : { $lte : rand } } )
}

모두 쿼리 $gte$lte필요한 것은 임의의 숫자 가까운과 문서를 찾을 수rand .

물론 임의의 필드를 색인화하고 싶을 것입니다.

db.docs.ensureIndex( { key : 1, random :1 } )

인덱스에 대해 이미 쿼리하는 경우 인덱스를 삭제 random: 1하고 추가 한 후 다시 추가하십시오.


답변

MongoDB의 지리 공간 색인 기능을 사용하여 ‘가장 가까운’문서를 임의의 숫자로 선택할 수 있습니다.

먼저 컬렉션에서 지형 공간 색인 생성을 활성화합니다.

db.docs.ensureIndex( { random_point: '2d' } )

X 축에서 임의의 점으로 문서 묶음을 만들려면 :

for ( i = 0; i < 10; ++i ) {
    db.docs.insert( { key: i, random_point: [Math.random(), 0] } );
}

그런 다음 컬렉션에서 임의의 문서를 다음과 같이 얻을 수 있습니다.

db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )

또는 임의의 지점에 가장 가까운 여러 문서를 검색 할 수 있습니다.

db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

여기에는 쿼리가 하나만 필요하고 null 검사가 필요하지 않으며 코드는 깨끗하고 단순하며 유연합니다. 지오 포인트의 Y 축을 사용하여 쿼리에 두 번째 임의성 차원을 추가 할 수도 있습니다.


답변

다음 레시피는 몽고 요리 책 솔루션보다 약간 느리지 만 (모든 문서에 임의의 키를 추가하십시오) 더 균등하게 분산 된 임의의 문서를 반환합니다. skip( random )솔루션 보다 약간 덜 고르지 만 문서가 제거되는 경우 훨씬 빠르고 안전합니다.

function draw(collection, query) {
    // query: mongodb query object (optional)
    var query = query || { };
    query['random'] = { $lte: Math.random() };
    var cur = collection.find(query).sort({ rand: -1 });
    if (! cur.hasNext()) {
        delete query.random;
        cur = collection.find(query).sort({ rand: -1 });
    }
    var doc = cur.next();
    doc.random = Math.random();
    collection.update({ _id: doc._id }, doc);
    return doc;
}

또한 문서에 임의의 “무작위”필드를 추가해야하므로 문서를 만들 때 반드시 추가해야합니다. Geoffrey가 표시 한대로 컬렉션을 초기화해야 할 수도 있습니다.

function addRandom(collection) {
    collection.find().forEach(function (obj) {
        obj.random = Math.random();
        collection.save(obj);
    });
}
db.eval(addRandom, db.things);

벤치 마크 결과

이 방법은 skip() (ceejayoz의) 방법 Michael이보고 한 “요리 책”방법보다 더 균일 한 임의의 문서를 생성합니다.

1,000,000 개의 요소가있는 컬렉션의 경우 :

  • 이 방법은 내 컴퓨터에서 1 밀리 초도 채 걸리지 않습니다

  • skip()방법은 평균 180ms 소요

요리 책 방법은 임의의 숫자가 선호하지 않기 때문에 많은 수의 문서를 선택하지 않습니다.

  • 이 방법은 시간이 지남에 따라 모든 요소를 ​​고르게 선택합니다.

  • 내 벤치 마크에서는 요리 책 방법보다 30 % 느 렸습니다.

  • 무작위성은 100 % 완벽하지는 않지만 매우 좋습니다 (필요한 경우 개선 될 수 있음)

이 레시피는 완벽하지 않습니다. 완벽한 솔루션은 다른 사람들이 언급했듯이 기본 제공 기능입니다.
그러나 많은 목적을 위해 좋은 절충안이되어야합니다.


답변

다음은 약간의 수학 및 논리에 ObjectId대한 기본값을 사용하는 방법 _id입니다.

// Get the "min" and "max" timestamp values from the _id in the collection and the 
// diff between.
// 4-bytes from a hex string is 8 characters

var min = parseInt(db.collection.find()
        .sort({ "_id": 1 }).limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    max = parseInt(db.collection.find()
        .sort({ "_id": -1 })limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    diff = max - min;

// Get a random value from diff and divide/multiply be 1000 for The "_id" precision:
var random = Math.floor(Math.floor(Math.random(diff)*diff)/1000)*1000;

// Use "random" in the range and pad the hex string to a valid ObjectId
var _id = new ObjectId(((min + random)/1000).toString(16) + "0000000000000000")

// Then query for the single document:
var randomDoc = db.collection.find({ "_id": { "$gte": _id } })
   .sort({ "_id": 1 }).limit(1).toArray()[0];

이것이 쉘 표현의 일반적인 논리이며 쉽게 적용 할 수 있습니다.

그래서 포인트에서 :

  • 컬렉션에서 최소 및 최대 기본 키 값 찾기

  • 해당 문서의 타임 스탬프 사이에있는 임의의 숫자를 생성하십시오.

  • 최소값에 난수를 추가하고 해당 값보다 크거나 같은 첫 번째 문서를 찾으십시오.

이것은 “16 진”의 타임 스탬프 값에서 “패딩”을 사용하여 ObjectId우리가 찾고있는 것이므로 유효한 값 을 형성합니다 . 정수를 _id값 으로 사용하는 것은 본질적으로 간단하지만 포인트의 기본 아이디어는 동일합니다.


답변

Python에서 pymongo를 사용하는 경우 :

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]