Programing

MongoDB의 무작위 레코드

lottogame 2020. 3. 16. 08:12
반응형

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)]

키 오프 할 데이터가 없으면 힘들다. _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);

이제 집계를 사용할 수 있습니다. 예:

db.users.aggregate(
   [ { $sample: { size: 3 } } ]
)

문서를 참조하십시오 .


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

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)
    };
}();

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

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

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


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;
}

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

  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)
                });
            });
    

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

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

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


임의의 값이 주어진 확률보다 높을 때만 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)은 검색 할 문서 수입니다.

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


임의의 _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)
            })
        })
    })

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


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

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

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

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

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


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

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

몽구스를 사용하는 경우 몽구스 랜덤 몽구스 랜덤을 사용할 수 있습니다


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

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

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를 배열로 수집 한 다음 선택하는 대신 인덱스 조회에 내장 된 경우 엄청나게 도움이 될 것입니다. (투표하세요!)


이것은 잘 작동하고 빠르며 여러 문서와 함께 작동하며 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 질문 에서 임의의 레코드를 찾는 방법 은이 질문의 복제본으로 표시됩니다. 차이점은이 질문에 명시 적으로 임의 문서 얻기에 관하여 다른 하나 하나의 레코드에 대해 명시 적으로 요구한다는 것입니다 .


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
        ]
    ];

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

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

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


문서 대 오브젝트 랩퍼 인 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

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


나는 이런 식으로 사용하고 있습니다

db.collection.aggregate(
   [ { $sample: { size: 5 } } ]
)

전체 문서 https://docs.mongodb.com/manual/reference/operator/aggregation/sample/


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

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

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

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

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

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

참고 URL : https://stackoverflow.com/questions/2824157/random-record-from-mongodb

반응형