我想从一个巨大的集合(1亿条记录)中获得一个随机记录。

最快最有效的方法是什么?

数据已经在那里,没有字段可以生成随机数并获得随机行。


当前回答

为了获得确定数量的无重复的随机文档:

first get all ids get size of documents loop geting random index and skip duplicated 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) }); });

其他回答

我建议使用map/reduce,其中使用map函数只在随机值高于给定概率时发出。

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函数可以工作,因为map函数只发出一个键('1')。

“probability”的值在“scope”中定义,当调用mapRreduce(…)

像这样使用mapReduce在分片数据库上也可以使用。

如果你想从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)是数据库中的文档数量,“count子集”(n)是要检索的文档数量。

这种方法可能会在分片数据库上产生一些问题。

您还可以在执行查询后使用shuffle-array

Var shuffle = require('shuffle-array');

Accounts.find (qry函数(呃,results_array) { newIndexArr = shuffle (results_array);

我建议给每个对象添加一个随机的int字段。然后你就可以做

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

随机选择一个文档。只要确保你ensureIndex({random_field:1})

下面是一种使用_id的默认ObjectId值和一些数学和逻辑的方法。

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

这是shell表示法的一般逻辑,很容易适应。

所以在点上:

查找集合中的最小和最大主键值 生成一个位于这些文档的时间戳之间的随机数。 将随机数与最小值相加,然后找到大于或等于该值的第一个文档。

这使用了从“十六进制”的时间戳值中“填充”来形成有效的ObjectId值,因为这就是我们正在寻找的。使用整数作为_id值本质上更简单,但在点中基本思想相同。

下面的聚合操作从集合中随机选择3个文档:

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

https://docs.mongodb.com/manual/reference/operator/aggregation/sample/