我想从一个巨大的集合(1亿条记录)中获得一个随机记录。
最快最有效的方法是什么?
数据已经在那里,没有字段可以生成随机数并获得随机行。
我想从一个巨大的集合(1亿条记录)中获得一个随机记录。
最快最有效的方法是什么?
数据已经在那里,没有字段可以生成随机数并获得随机行。
当前回答
MongoDB 3.2更新
3.2在聚合管道中引入了$sample。
还有一篇很好的博客文章是关于如何将其付诸实践的。
对于旧版本(以前的答案)
这实际上是一个功能请求:http://jira.mongodb.org/browse/SERVER-533,但它是在“不会修复”下提交的。
烹饪书中有一个非常好的方法,可以从集合中随机选择一个文档: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附加到它,然后再次添加它。
其他回答
对于我来说,我想以随机顺序获得相同的记录,所以我创建了一个用于排序的空数组,然后生成1到7之间的随机数(我有7个字段)。每次我得到一个不同的值,我分配一个不同的随机排序。 这是“外行”,但对我来说很管用。
//generate random number
const randomval = some random value;
//declare sort array and initialize to empty
const sort = [];
//write a conditional if else to get to decide which sort to use
if(randomval == 1)
{
sort.push(...['createdAt',1]);
}
else if(randomval == 2)
{
sort.push(...['_id',1]);
}
....
else if(randomval == n)
{
sort.push(...['n',1]);
}
我对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;
}
您还可以使用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 )
这只需要一个查询,没有空检查,加上代码干净,简单和灵活。您甚至可以使用地理点的y轴为查询添加第二个随机性维度。
使用Map/Reduce,您当然可以获得一个随机记录,只是不一定非常有效,这取决于您最终使用的过滤集合的大小。
我已经用5万个文档测试了这个方法(过滤器将其减少到大约3万个),它在Intel i3、16GB ram和SATA3 HDD上执行大约400毫秒……
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。在我的例子中,我测试了5万个可能的文档中的大约3万个。
Reduce函数只是在数组中从0到项数(-1)之间选择一个随机整数,然后从数组中返回该_id。
400ms听起来是一段很长的时间,而且确实如此,如果您有5000万条记录而不是5万条记录,这可能会增加开销,以至于在多用户情况下无法使用。
MongoDB在核心中包含这个功能有一个悬而未决的问题…https://jira.mongodb.org/browse/SERVER-533
如果将这种“随机”选择构建到索引查找中,而不是将id收集到一个数组中然后选择一个,这将非常有帮助。(去投票吧!)
您可以选择一个随机时间戳,然后搜索随后创建的第一个对象。 它将只扫描单个文档,尽管它不一定会给您一个统一的分布。
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)
};
}();