MongoDB의 무작위 레코드


336

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

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

어떤 제안?


2
제목이 "몽고에서 무작위로 결과 세트를 주문하기"라는 제목의 SO 질문 도 참조하십시오 . 결과 집합을 무작위로 정렬하는 것을 생각하는 것이이 질문의보다 일반적인 버전입니다. 더 강력하고 유용합니다.
David J.

11
이 질문은 계속 뜹니다. 기능 요청 에서 MongoDB 티켓 추적기 의 콜렉션 에서 임의의 항목을 가져 오기 위한 최신 정보를 찾을 수 있습니다 . 기본적으로 구현되는 경우 가장 효율적인 옵션 일 수 있습니다. (기능을 원하면 투표하십시오.)
David J.

이 조각 모음입니까?
Dylan Tong

3
정답은 아래 @JohnnyHK에 의해 주어졌습니다 : db.mycoll.aggregate ({$ sample : {size : 1}})
Florian

누구든지 이것이 첫 번째 기록을 취하는 것보다 얼마나 느린 지 알고 있습니까? 나는 무언가를하기 위해 무작위 샘플을 가져 와서 순서대로 수행하는 것이 가치가 있는지 토론하고 있습니다.
David Kong

답변:


248

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보다 크면 반환 된 문서 샘플에 중복이있을 수 있습니다.


12
이것은 좋은 방법이지만 샘플에 동일한 객체의 사본이 없음을 보증하지는 않습니다.
Matheus Araujo

10
@MatheusAraujo 당신이 하나의 기록을 원하지만 어쨌든 좋은 점을 원한다면 중요하지 않습니다
Toby

3
비판적이지는 않지만 질문에 MongoDB 버전이 지정되어 있지 않으므로 최신 버전을 사용하는 것이 합리적이라고 가정합니다.
dalanmiller

2
@Nepoxx 관련 처리에 관한 문서 를 참조하십시오 .
JohnnyHK

2
@brycejl $ sample 단계에서 일치하는 문서를 선택하지 않으면 일치하지 않는 치명적인 결함이 있습니다.
JohnnyHK

115

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

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

139
불행히도 skip ()은 많은 문서를 스캔해야하기 때문에 다소 비효율적입니다. 또한 개수를 가져오고 쿼리를 실행하는 사이에 행이 제거되면 경쟁 조건이 있습니다.
mstearn

6
난수는 0과 개수 사이에 있어야합니다 (제외). 즉, 10 개의 항목이있는 경우 임의의 숫자는 0과 9 사이 여야합니다. 그렇지 않으면 커서가 마지막 항목을 건너 뛰려고 시도해도 아무 것도 반환되지 않습니다.
matt

4
고마워, 내 목적을 위해 완벽하게 일했다. @mstearn, 효율성과 경쟁 조건에 대한 귀하의 의견은 유효하지만, 문제가없는 컬렉션 (레코드가 삭제되지 않은 컬렉션의 일회성 서버 측 배치 추출)의 경우 해킹 (IMO)보다 훨씬 우수합니다 Mongo Cookbook의 솔루션.
Michael Moussa

4
한계를 -1로 설정하면 어떻게됩니까?
MonkeyBonkey

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "numberToReturn이 0이면 db는 기본 반환 크기를 사용합니다. 숫자가 음수이면 데이터베이스는 해당 숫자를 반환하고 커서를 닫습니다. "
ceejayoz

86

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하고 추가 한 후 다시 추가하십시오.


7
다음은 컬렉션의 모든 문서에 임의 필드를 추가하는 간단한 방법입니다. function setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey 2016 년

8
문서를 무작위로 선택하지만 두 번 이상 수행하면 조회가 독립적이지 않습니다. 무작위 확률이 지시하는 것보다 동일한 문서를 두 번 연속으로 얻을 가능성이 높습니다.
결핍

12
순환 해싱의 잘못된 구현처럼 보입니다. 부족한 것보다 훨씬 나쁩니다. 난수가 고르게 분포되지 않기 때문에 하나의 조회조차도 편향됩니다. 이 작업을 제대로 수행하려면 문서 당 10 개의 난수 세트가 필요합니다. 문서 당 더 많은 난수를 사용할수록 출력 분포가 더 균일 해집니다.
Thomas

4
MongoDB JIRA 티켓은 여전히 ​​살아 있습니다 : jira.mongodb.org/browse/SERVER-533 이 기능을 원하면 의견을 남기고 투표하십시오.
David J.

1
언급 된주의 유형에 유의하십시오. 적은 양의 문서에서는 효율적으로 작동하지 않습니다. 임의의 키가 3과 63 인 두 개의 항목이 제공됩니다. 문서 # 63 $gte은 처음에 더 자주 선택됩니다 . 이 경우 대체 솔루션 stackoverflow.com/a/9499484/79201 이 더 잘 작동합니다.
Ryan Schumacher

56

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 축을 사용하여 쿼리에 두 번째 임의성 차원을 추가 할 수도 있습니다.


8
나는이 대답을 좋아한다. 내가 본 가장 효율적인 것은 서버 측에 대한 많은 혼란을 필요로하지 않는다.
Tony Million

4
이것은 또한 주변에 몇 가지 점이있는 문서에 편향되어 있습니다.
Thomas

6
이는 사실이며 다른 문제도 있습니다. 문서는 임의의 키와 밀접하게 관련되어 있으므로 여러 문서를 선택하면 어떤 문서가 그룹으로 반환되는지 매우 예측할 수 있습니다. 또한 경계 (0 및 1)에 가까운 문서를 선택할 가능성이 적습니다. 후자는 가장자리를 감싸는 구형 지오 매핑을 사용하여 해결할 수 있습니다. 그러나이 답변은 완벽한 임의 선택 메커니즘이 아니라 향상된 요리 책 레시피 버전으로보아야합니다. 대부분의 목적을 위해 충분히 무작위입니다.
Nico de Poel

@NicodePoel, 나는 당신의 의견뿐만 아니라 당신의 의견을 좋아합니다! 그리고 나는 당신을 위해 몇 가지 질문을 가지고 있습니다 : 1- 0과 1에 가까운 지점이 선택 될 가능성이 적다는 것을 어떻게 알 수 있습니까, 어떤 수학적 근거에 기초한 것입니까?, 2- 구형 지오 매핑에 대해 더 자세히 설명 할 수 있습니까? 무작위 선택이 더 나은 방법과 MongoDB에서 어떻게 수행합니까? ... 감사합니다!
securecurve

당신의 아이디어를 풍부하게하십시오. 마지막으로 CPU 및 RAM에 많은 훌륭한 코드가 있습니다! 감사합니다
Qais Bsharat

21

다음 레시피는 몽고 요리 책 솔루션보다 약간 느리지 만 (모든 문서에 임의의 키를 추가하십시오) 더 균등하게 분산 된 임의의 문서를 반환합니다. 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 % 완벽하지는 않지만 매우 좋습니다 (필요한 경우 개선 될 수 있음)

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


10

다음은 약간의 수학 및 논리에 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값 으로 사용하는 것은 본질적으로 간단하지만 포인트의 기본 아이디어는 동일합니다.


3 억 라인의 컬렉션이 있습니다. 이것이 작동하는 유일한 솔루션이며 충분히 빠릅니다.
Nikos

8

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

import random

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

5
내부적으로 주목할 가치가있는 것은 다른 많은 답변과 마찬가지로 건너 뛰기와 제한을 사용합니다.
JohnnyHK

당신의 대답은 맞습니다. 그러나 교체하십시오 count()estimated_document_count()같은 count()Mongdo의 버전 4.2에서 더 이상 사용되지 않습니다.
user3848207


6

키 오프 할 데이터가 없으면 힘들다. _id 필드는 무엇입니까? 그들은 mongodb 객체 ID입니까? 그렇다면 가장 높은 값과 가장 낮은 값을 얻을 수 있습니다.

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

그런 다음 id가 균등하게 분포되어 있다고 가정하면 (그러나 그것들은 그렇지 않지만 적어도 시작입니다) :

unsigned long long L = first_8_bytes_of(lowest)
unsigned long long H = first_8_bytes_of(highest)

V = (H - L) * random_from_0_to_1();
N = L + V;
oid = N concat random_4_bytes();

randomobj = db.coll.find({_id:{$gte:oid}}).limit(1);

1
어떤 아이디어가 PHP에서 어떻게 보일까요? 아니면 적어도 어떤 언어를 사용하셨습니까? 파이썬인가요?
Marcin

6

Python (pymongo)을 사용하면 집계 함수도 작동합니다.

collection.aggregate([{'$sample': {'size': sample_size }}])

이 방법은 난수에 대한 쿼리를 실행하는 보다 훨씬 빠릅니다 (예 : collection.find ([random_int]). 이는 특히 대규모 컬렉션의 경우에 해당합니다.


5

임의의 타임 스탬프를 선택하고 나중에 생성 된 첫 번째 개체를 검색 할 수 있습니다. 단일 문서 만 스캔하지만 반드시 균일 한 배포를 제공하지는 않습니다.

var randRec = function() {
    // replace with your collection
    var coll = db.collection
    // get unixtime of first and last record
    var min = coll.find().sort({_id: 1}).limit(1)[0]._id.getTimestamp() - 0;
    var max = coll.find().sort({_id: -1}).limit(1)[0]._id.getTimestamp() - 0;

    // allow to pass additional query params
    return function(query) {
        if (typeof query === 'undefined') query = {}
        var randTime = Math.round(Math.random() * (max - min)) + min;
        var hexSeconds = Math.floor(randTime / 1000).toString(16);
        var id = ObjectId(hexSeconds + "0000000000000000");
        query._id = {$gte: id}
        return coll.find(query).limit(1)
    };
}();

수퍼 선형 데이터베이스 증가를 설명하기 위해 임의의 날짜를 쉽게 왜곡 할 수 있습니다.
Martin Nowak

이 여기가 다른 솔루션에 사용되는 O (1), unline은 ((건너 뛰기) 또는 계산)에서 작동, 매우 큰 컬렉션을위한 최선의 방법입니다
대리석

4

PHP에 대한 내 솔루션 :

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}

3

중복없이 결정된 수의 무작위 문서를 얻으려면 :

  1. 먼저 모든 ID를 얻습니다.
  2. 문서의 크기를 얻다
  3. 임의의 인덱스를 얻는 루프와 중복 된 건너 뛰기

    number_of_docs=7
    db.collection('preguntas').find({},{_id:1}).toArray(function(err, arr) {
    count=arr.length
    idsram=[]
    rans=[]
    while(number_of_docs!=0){
        var R = Math.floor(Math.random() * count);
        if (rans.indexOf(R) > -1) {
         continue
          } else {           
                   ans.push(R)
                   idsram.push(arr[R]._id)
                   number_of_docs--
                    }
        }
    db.collection('preguntas').find({}).toArray(function(err1, doc1) {
                    if (err1) { console.log(err1); return;  }
                   res.send(doc1)
                });
            });

2

임의의 값이 주어진 확률보다 높을 때만 map 함수를 사용하여 map / reduce를 사용하는 것이 좋습니다.

function mapf() {
    if(Math.random() <= probability) {
    emit(1, this);
    }
}

function reducef(key,values) {
    return {"documents": values};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": { "probability": 0.5}});
printjson(res.results);

위의 reducef 함수는 맵 함수에서 하나의 키 ( '1') 만 방출되기 때문에 작동합니다.

"확률"의 값은 mapRreduce (...)를 호출 할 때 "범위"에 정의됩니다.

이와 같이 mapReduce를 사용하면 샤드 DB에서도 사용할 수 있습니다.

db에서 정확히 n 개의 m 개의 문서를 선택하려면 다음과 같이하십시오.

function mapf() {
    if(countSubset == 0) return;
    var prob = countSubset / countTotal;
    if(Math.random() <= prob) {
        emit(1, {"documents": [this]}); 
        countSubset--;
    }
    countTotal--;
}

function reducef(key,values) {
    var newArray = new Array();
for(var i=0; i < values.length; i++) {
    newArray = newArray.concat(values[i].documents);
}

return {"documents": newArray};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": {"countTotal": 4, "countSubset": 2}})
printjson(res.results);

여기서 "countTotal"(m)은 db의 문서 수이고 "countSubset"(n)은 검색 할 문서 수입니다.

이 방법은 샤드 데이터베이스에서 일부 문제를 야기 할 수 있습니다.


4
하나의 요소를 리턴하기 위해 전체 콜렉션 스캔을 수행하는 것이 가장 효율적인 방법이어야합니다.
Thomas

1
트릭은 임의의 수의 임의의 요소를 반환하는 일반적인 솔루션입니다.이 경우 임의의 2 이상의 요소를 가져올 때 다른 솔루션보다 빠릅니다.
torbenl

2

임의의 _id를 선택하고 해당 객체를 반환 할 수 있습니다.

 db.collection.count( function(err, count){
        db.collection.distinct( "_id" , function( err, result) {
            if (err)
                res.send(err)
            var randomId = result[Math.floor(Math.random() * (count-1))]
            db.collection.findOne( { _id: randomId } , function( err, result) {
                if (err)
                    res.send(err)
                console.log(result)
            })
        })
    })

여기서는 수집에 임의의 숫자를 저장하는 데 공간을 소비 할 필요가 없습니다.


1

각 객체에 임의의 int 필드를 추가하는 것이 좋습니다. 그럼 당신은 할 수 있습니다

findOne({random_field: {$gte: rand()}}) 

임의의 문서를 선택합니다. Index ({random_field : 1})를 확인하십시오.


2
컬렉션의 첫 번째 레코드의 random_field 값이 비교적 높은 경우 거의 항상 반환되지 않습니까?
thehiatus

2
thehaitus는 정확합니다 – 어떤 목적에도 적합하지 않습니다
Heptic

7
이 솔루션은 완전히 잘못되었습니다. 임의의 숫자를 추가해도 (0에서 2 ^ 32-1 사이에서 상상해 봅시다) 좋은 분포를 보장하지 않으며 $ gte를 사용하면 무작위 선택이 가깝지 않기 때문에 최악의 상황이됩니다. 의사 난수로. 나는이 개념을 절대로 사용하지 말 것을 권한다.
Maximiliano Rios

1

비슷한 솔루션에 직면했을 때 나는 역 추적하고 비즈니스 요청이 실제로 제시되는 인벤토리의 어떤 형태의 회전을 생성하기위한 것이라는 것을 알았습니다. 이 경우에는 MongoDB와 같은 데이터 저장소가 아닌 Solr과 같은 검색 엔진의 응답이있는 훨씬 더 나은 옵션이 있습니다.

간단히 말해서, 내용을 "지능적으로 회전"해야한다는 요구 사항에 따라, 모든 문서에서 임의의 숫자 대신에해야 할 일은 개인 q 점수 수정자를 포함하는 것입니다. 적은 수의 사용자를 가정하여 직접 구현하기 위해 productId, 노출 수, 클릭 수, 마지막으로 본 날짜 및 비즈니스가 aq 점수를 계산하는 데 의미가 있다고 판단하는 기타 요소가있는 사용자 당 문서를 저장할 수 있습니다. 수정 자. 표시 할 집합을 검색 할 때 일반적으로 최종 사용자가 요청한 것보다 더 많은 문서를 데이터 저장소에서 요청한 다음 q 점수 수정자를 적용하고 최종 사용자가 요청한 레코드 수를 가져온 다음 결과 페이지를 무작위로 무작위 화합니다. 응용 프로그램 계층 (메모리)의 문서를 정렬하기 만하면됩니다.

사용자 유니버스가 너무 큰 경우 사용자를 동작 그룹으로 분류하고 사용자가 아닌 동작 그룹별로 인덱싱 할 수 있습니다.

제품 유니버스가 충분히 작은 경우 사용자 당 인덱스를 만들 수 있습니다.

이 기술이 훨씬 효율적이지만 소프트웨어 솔루션을 사용하는 관련 가치 있고 가치있는 경험을 만드는 데 더 중요하다는 것을 알았습니다.


1

해결책 중 어느 것도 나를 위해 잘 작동하지 않았습니다. 특히 간격이 많고 세트가 작을 때. 이것은 나를 위해 아주 잘 작동했습니다 (PHP에서) :

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();

언어를 지정하지만 사용중인 라이브러리는 지정하지 않습니까?
Benjamin

참고로, 첫 번째 줄과 세 번째 줄 사이에 문서가 제거되면 경쟁 조건이 있습니다. 또한 find+ skip는 매우 나쁩니다. 하나를 선택하기 위해 모든 문서를 반환합니다.
Martin Konecny


1

RANDOM 솔루션별로 내 PHP / MongoDB 정렬 / 순서. 이것이 누군가를 돕기를 바랍니다.

참고 : MongoDB 컬렉션에는 MySQL 데이터베이스 레코드를 나타내는 숫자 ID가 있습니다.

먼저 무작위로 생성 된 10 개의 숫자로 배열을 만듭니다.

    $randomNumbers = [];
    for($i = 0; $i < 10; $i++){
        $randomNumbers[] = rand(0,1000);
    }

내 집계에서는 $ arrayElemAt 및 $ mod (모듈러스)와 결합 된 $ addField 파이프 라인 연산자를 사용합니다. 모듈러스 연산자는 0에서 9 사이의 숫자를 제공하며 무작위로 생성 된 숫자가있는 배열에서 숫자를 선택하는 데 사용합니다.

    $aggregate[] = [
        '$addFields' => [
            'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
        ],
    ];

그런 다음 정렬 파이프 라인을 사용할 수 있습니다.

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];

0

간단한 ID 키가 있으면 모든 ID를 배열에 저장 한 다음 임의의 ID를 선택할 수 있습니다. (루비 답변) :

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first

0

Map / Reduce를 사용하면 결과적으로 필터링 된 컬렉션의 크기에 따라 반드시 효율적으로 무작위 레코드를 얻을 수는 없습니다.

나는이 방법을 50,000 문서 (필터는 약 30,000으로 줄였습니다)로 테스트 했으며 16GB 램과 SATA3 HDD가있는 Intel i3 에서 약 400ms 에서 실행됩니다 ...

db.toc_content.mapReduce(
    /* map function */
    function() { emit( 1, this._id ); },

    /* reduce function */
    function(k,v) {
        var r = Math.floor((Math.random()*v.length));
        return v[r];
    },

    /* options */
    {
        out: { inline: 1 },
        /* Filter the collection to "A"ctive documents */
        query: { status: "A" }
    }
);

Map 함수는 단순히 쿼리와 일치하는 모든 문서의 ID 배열을 만듭니다. 필자의 경우 50,000 가지 가능한 문서 중 약 30,000으로 이것을 테스트했습니다.

Reduce 함수는 단순히 0에서 배열의 항목 수 (-1) 사이의 임의의 정수를 선택한 다음 배열에서 해당 _id 를 반환합니다 .

400ms는 오랫동안 들리는 것처럼 들립니다. 실제로 5 만 개가 아닌 5 천만 개의 레코드가 있으면 다중 사용자 상황에서 사용할 수없는 지점까지 오버 헤드가 증가 할 수 있습니다.

MongoDB 가이 기능을 핵심에 포함시키는 공개적인 문제가 있습니다 ... https://jira.mongodb.org/browse/SERVER-533

이 "랜덤"선택이 ID를 배열로 수집 한 다음 선택하는 대신 인덱스 조회에 내장 된 경우 엄청나게 도움이됩니다. (투표하세요!)


0

이것은 잘 작동하고 빠르며 여러 문서와 함께 작동하며 rand채우기 필드 가 필요하지 않으므로 결국 자체적으로 채워집니다.

  1. 컬렉션의 .rand 필드에 색인 추가
  2. 찾기 및 새로 고침을 사용하십시오.
// Install packages:
//   npm install mongodb async
// Add index in mongo:
//   db.ensureIndex('mycollection', { rand: 1 })

var mongodb = require('mongodb')
var async = require('async')

// Find n random documents by using "rand" field.
function findAndRefreshRand (collection, n, fields, done) {
  var result = []
  var rand = Math.random()

  // Append documents to the result based on criteria and options, if options.limit is 0 skip the call.
  var appender = function (criteria, options, done) {
    return function (done) {
      if (options.limit > 0) {
        collection.find(criteria, fields, options).toArray(
          function (err, docs) {
            if (!err && Array.isArray(docs)) {
              Array.prototype.push.apply(result, docs)
            }
            done(err)
          }
        )
      } else {
        async.nextTick(done)
      }
    }
  }

  async.series([

    // Fetch docs with unitialized .rand.
    // NOTE: You can comment out this step if all docs have initialized .rand = Math.random()
    appender({ rand: { $exists: false } }, { limit: n - result.length }),

    // Fetch on one side of random number.
    appender({ rand: { $gte: rand } }, { sort: { rand: 1 }, limit: n - result.length }),

    // Continue fetch on the other side.
    appender({ rand: { $lt: rand } }, { sort: { rand: -1 }, limit: n - result.length }),

    // Refresh fetched docs, if any.
    function (done) {
      if (result.length > 0) {
        var batch = collection.initializeUnorderedBulkOp({ w: 0 })
        for (var i = 0; i < result.length; ++i) {
          batch.find({ _id: result[i]._id }).updateOne({ rand: Math.random() })
        }
        batch.execute(done)
      } else {
        async.nextTick(done)
      }
    }

  ], function (err) {
    done(err, result)
  })
}

// Example usage
mongodb.MongoClient.connect('mongodb://localhost:27017/core-development', function (err, db) {
  if (!err) {
    findAndRefreshRand(db.collection('profiles'), 1024, { _id: true, rand: true }, function (err, result) {
      if (!err) {
        console.log(result)
      } else {
        console.error(err)
      }
      db.close()
    })
  } else {
    console.error(err)
  }
})

추신. mongodb 질문 에서 임의의 레코드를 찾는 방법 은이 질문의 복제본으로 표시됩니다. 차이점은이 질문에 명시 적으로 임의 문서 얻기에 관하여 다른 하나 하나의 레코드에 대해 명시 적으로 요구한다는 것입니다 .


-2

문서 대 오브젝트 랩퍼 인 mongoid를 사용하는 경우 Ruby에서 다음을 수행 할 수 있습니다. (모델이 사용자라고 가정)

User.all.to_a[rand(User.count)]

내 .irbrc에서 나는

def rando klass
    klass.all.to_a[rand(klass.count)]
end

레일 콘솔에서 예를 들어

rando User
rando Article

컬렉션에서 임의로 문서를 가져옵니다.


1
전체 컬렉션을 배열로 읽은 다음 하나의 레코드를 선택하므로 매우 비효율적입니다.
JohnnyHK

좋아, 비효율적이지만 확실히 편리합니다. 당신의 데이터 크기가 너무 크지 않으면 이것을 시도하십시오
Zack Xu

3
물론, 원래 질문은 1 억 개의 문서가있는 컬렉션에 대한 것이 었으므로이 경우에는 매우 나쁜 해결책이 될 것입니다!
JohnnyHK

-2

셔플 배열을 사용할 수도 있습니다쿼리를 실행 한 후 을

var shuffle = 필요 ( '셔플 배열');

Accounts.find (qry, function (err, results_array) {newIndexArr = 셔플 (results_array);


-7

효율적이고 안정적으로 작동하는 것은 다음과 같습니다.

각 문서에 "random"이라는 필드를 추가하고 임의의 값을 할당하고 임의 필드에 대한 색인을 추가 한 후 다음과 같이 진행하십시오.

"links"라는 웹 링크 모음이 있고 그로부터 임의의 링크를 원한다고 가정합니다.

link = db.links.find().sort({random: 1}).limit(1)[0]

동일한 링크가 두 번째로 나타나지 않도록하려면 임의의 필드를 새로운 임의의 숫자로 업데이트하십시오.

db.links.update({random: Math.random()}, link)

2
다른 임의의 키를 선택할 수 있는데 왜 데이터베이스를 업데이트 해야합니까?
Jason S

임의로 선택할 키 목록이 없을 수 있습니다.
Mike

매번 전체 컬렉션을 정렬해야합니까? 그리고 큰 난수를 가진 불행한 기록은 어떻습니까? 그들은 선택되지 않습니다.
Fantius

1
다른 솔루션, 특히 MongoDB 책에서 제안 된 솔루션이 작동하지 않기 때문에이 작업을 수행해야합니다. 첫 번째 찾기가 실패하면 두 번째 찾기는 항상 가장 작은 임의의 값을 가진 항목을 반환합니다. 무작위로 내림차순으로 색인을 생성하면 첫 번째 쿼리는 항상 가장 큰 난수를 가진 항목을 반환합니다.
trainwreck 2012 년

각 문서에 필드를 추가 하시겠습니까? 나는 그것이 바람직하지 않다고 생각합니다.
CS_noob
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.