MongoDB的随机记录

MongoDB的随机记录,mongodb,random,mongodb-query,Mongodb,Random,Mongodb Query,我希望从一个巨大的1亿记录mongodb中获得一个随机记录 最快和最有效的方法是什么?数据已经存在,并且没有可以生成随机数并获得随机行的字段 有什么建议吗?对所有记录进行计数,生成一个介于0和计数之间的随机数,然后执行以下操作: db.yourCollection.find().limit(-1).skip(yourRandomNumber).next() 如果没有数据可供检索,那就很难了。什么是_id字段?它们是mongodb对象id吗?如果是,则可以获得最高和最低值: lowest = d

我希望从一个巨大的1亿记录mongodb中获得一个随机记录

最快和最有效的方法是什么?数据已经存在,并且没有可以生成随机数并获得随机行的字段


有什么建议吗?

对所有记录进行计数,生成一个介于0和计数之间的随机数,然后执行以下操作:

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

如果没有数据可供检索,那就很难了。什么是_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);

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

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

随机选取一个文档。只要确保索引{random_field:1}

高效可靠的工作原理如下:

向每个文档添加一个名为random的字段,并为其指定一个随机值,为该随机字段添加索引,然后按如下步骤进行操作:

假设我们有一个名为链接的web链接集合,我们希望从中获得一个随机链接:

link = db.links.find().sort({random: 1}).limit(1)[0]
为确保同一链接不会再次弹出,请使用新的随机数更新其随机场:

db.links.update({random: Math.random()}, link)
MongoDB 3.2的更新 3.2引入聚合管道

把它付诸实践也有好处

对于较旧的版本,请使用以前的答案 这实际上是一个功能请求:但它是在“不会修复”下提交的

食谱中有一个很好的方法可以从集合中随机选择一个文档:

要解释配方,请将随机数指定给文档:

db.docs.save( { key : 1, ..., random : Math.random() } )
const samples = (
  await Sample.aggregate([
    { $match: {} },
    { $sample: { size: 27 } },
    { $project: { _id: 1 } },
  ]).exec()
).map(v => v._id);

const mongooseSamples = await Sample.find({ _id: { $in: samples } });

console.log(mongooseSamples); //an Array of mongoose documents
然后选择一个随机文档:

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附加到它,然后再次添加它。

我建议使用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”

在调用mapRreduce时,概率值在范围中定义

像这样使用mapReduce在分片数据库上也应该是可用的

如果要从数据库中选择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);
db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )
其中,countTotal m是数据库中的文档数,countSubset n是要检索的文档数


这种方法可能会给分片数据库带来一些问题。

您还可以使用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.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

这只需要一个查询,不需要空检查,而且代码干净、简单、灵活。您甚至可以使用地质点的Y轴向查询中添加第二个随机维度。

如果您有一个简单的id键,您可以将所有id存储在一个数组中,然后选择一个随机id。Ruby回答:

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

当我面对类似的解决方案时,我回过头来发现业务请求实际上是为了创建某种形式的库存轮换。在这种情况下,有更好的选择,它们的答案来自Solr等搜索引擎,而不是MongoDB等数据存储

简而言之,由于需要智能地旋转内容,我们应该做的不是在所有文档中使用随机数,而是包括一个个人q分数修改器。为了自己实现这一点,假设用户数量很少,您可以为每个用户存储一个文档,其中包含productId、印象计数、点击计数、最后一次看到日期,以及业务部门认为对计算q分数修饰符有意义的任何其他因素。检索要显示的集合时,通常从数据存储中请求的文档比最终用户请求的文档多,然后应用q分数修饰符,获取最终用户请求的记录数,然后随机化结果页面,这是一个很小的集合,因此只需在内存中的应用层中对文档进行排序

如果用户范围太大,则可以将用户分类为行为组,并按行为组而不是用户索引

如果产品的范围足够小,您可以为每个用户创建一个索引


我发现这种技术效率更高,但更重要的是,在创建相关的、有价值的软件解决方案使用体验方面更有效。

如果您使用mongoid,文档到对象包装器,您可以在 红宝石假设您的模型是用户

User.all.to_a[rand(User.count)]
在我的.irbrc中,我有

def rando klass
    klass.all.to_a[rand(klass.count)]
end
所以在rails控制台中,我可以做,例如

rando User
rando Article

>从任何集合中随机获取文档。

没有一种解决方案对我很有效。特别是当有很多间隙且设置很小时。 这对我的php非常有效:

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

使用Map/Reduce,您当然可以获得一个随机记录,但根据最终使用的过滤集合的大小,不一定效率很高

我已经用50000个文档测试了这个方法。这个过滤器将它减少到大约30000个,它在一个带有16GB ram和SATA3硬盘的Intel i3上执行大约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数组。在我的例子中,我用50000个可能的文档中的大约30000个测试了这个

Reduce函数简单地选择一个介于0和数组中的项数-1之间的随机整数,然后从数组中返回该_id

400ms听起来很长时间,事实上,如果您有五千万条记录而不是五万条,这可能会增加开销,使其在多用户情况下变得不可用

MongoDB在核心中包含此功能还有一个悬而未决的问题


如果将此随机选择内置到索引查找中,而不是将ID收集到数组中然后选择一个,这将非常有帮助。去投票吧

下面的方法比mongo cookbook解决方案慢一点,在每个文档上添加一个随机键,但返回分布更均匀的随机文档。与skip random解决方案相比,它的分布稍不均匀,但在删除文档时速度更快,故障更安全

函数集合、查询{ //查询:mongodb查询对象可选 var query=query |{}; 查询['random']={$lte:Math.random}; var cur=collection.findquery.sort{rand:-1}; 如果!cur.hasNext{ 删除query.random; cur=collection.findquery.sort{rand:-1}; } var doc=cur.next; doc.random=Math.random; 更新{u id:doc.\u id},doc; 退货单; } 它还要求您向文档中添加一个随机字段,因此在创建文档时不要忘记添加:您可能需要初始化集合,如Geoffrey所示

函数addRandomcollection{ collection.find.forEachfunction obj{ obj.random=Math.random; collection.saveobj; }; } db.evaladdRandom,db.things; 基准结果

此方法比ceejayoz的skip方法快得多,生成的随机文档比Michael报告的cookbook方法更均匀:

对于包含1000000个元素的集合:

这种方法在我的机器上只需不到一毫秒

skip方法平均需要180毫秒

cookbook方法将导致大量文档永远不会被拾取,因为它们的随机数不利于它们

此方法将随时间均匀拾取所有图元

在我的基准测试中,它只比cookbook方法慢30%

随机性不是100%完美,但非常好,必要时可以改进

这个配方并不完美——正如其他人所指出的,完美的解决方案应该是内置功能。
但是,对于许多目的来说,它应该是一个很好的折衷方案。

这很好,速度很快,适用于多个文档,并且不需要填充rand字段,而rand字段最终会填充自身:

将索引添加到集合的.rand字段 使用查找和刷新,例如: //安装软件包: //npm安装mongodb异步 //在mongo中添加索引: //db.ensureIndex'mycollection',{rand:1} var mongodb=require'mongodb' var async=require'async' //使用rand字段查找n个随机文档。 函数findDefreshRand集合,n,字段,完成{ var结果=[] var rand=Math.random //根据条件和选项将文档追加到结果中,如果options.limit为0,则跳过调用。 var appender=功能标准、选项、完成{ 返回函数完成{ 如果options.limit>0{ collection.findcriteria、字段、选项.toArray 函数错误,文档{ if!err&&Array.isArraydocs{ Array.prototype.push.applyresult,文档 } 多纳尔 } }否则{ async.nextTickdone } } } 异步系列[ //使用unitialized.rand获取文档。 //注意:如果所有文档都已初始化,则可以注释掉此步骤。rand=Math.random 追加器{rand:{$exists:false}},{limit:n-result.length}, //在随机数的一侧取数。 appender{rand:{$gte:rand}},{sort:{rand:1},limit:n-result.length}, //继续在另一边取。 附录{rand:{$lt:rand}},{sort:{rand:-1},limit:n-result.length}, //刷新获取的文档(如果有)。 功能完成{ 如果result.length>0{ var批处理=收集 .InitializeUnderedbulkop{w:0} 对于var i=0;i问题标记为该问题的副本。区别在于,这个问题明确询问单个记录,而另一个问题明确询问获取随机文档。

您可以选择一个随机时间戳并搜索随后创建的第一个对象。 它只扫描一个文档,尽管它不一定给你一个统一的分发

var randRec=函数{ //替换为您的收藏 var coll=db.collection //获取第一条和最后一条记录的unixtime var min=coll.find.sort{u id:1}.limit1[0]。\u id.getTimestamp-0; var max=coll.find.sort{u id:-1}.limit1[0]。\u id.getTimestamp-0; //允许传递其他查询参数 返回函数查询{ 如果typeof查询=='undefined'查询={} var randTime=Math.roundMath.random*max-min+min; var hexSeconds=Math.floorrandTime/1000.toString16; 变量id=ObjectdExSeconds+0000000000000000; 查询。_id={$gte:id} return coll.findquery.limit1 }; }; 我的php解决方案:

/** *从Mongo获取随机文档 *@param$collection *@param$where *@param$fields *@param$limit *@author快乐代码 *@url happy-code.com */ 私有函数\u mongodb\u get\u随机MongoCollection$collection,$where=array,$fields=array,$limit=false{ //总文档数 $count=$collection->find$where,$fields->count; 如果!$limit{ //获取所有文档 $limit=$count; } $data=数组; 对于$i=0;$i<$limit;$i++{ //跳过文档 $skip=rand0,$count-1; 如果$skip!==0{ $doc=$collection->find$where,$fields->skip$skip->limit1->getNext; }否则{ $doc=$collection->find$where,$fields->limit1->getNext; } 如果是数组$doc{ //捕获文件 $data[$doc[''u id']->{'$id'}]=$doc; //在进行下一次迭代时忽略当前文档 $where[''u id']['$nin'][=$doc['u id']; } //每次迭代都会捕获文档并减少文档总数 $count-; } 返回$data; }
在使用pymongo的Python中:

import random

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

您可以选择random _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)
            })
        })
    })

在这里,您不需要在集合中存储随机数上花费空间。

这里有一种使用_id的默认值和一点数学和逻辑的方法

//从集合中的_id和 //区别于。 //十六进制字符串中的4字节为8个字符 var min=parseIntdb.collection.find .sort{u id:1}.limit1.toArray[0]。\u id.str.substr0,8,16*1000, max=parseIntdb.collection.find .sort{u id:-1}limit1.toArray[0]。{u id.str.substr0,8,16*1000, 差异=最大-最小; //从diff中获取一个随机值,并将其除以/乘以1000,以获得_id精度: var random=数学floorMath.floorMath.randomdiff*diff/1000*1000; //在范围内使用random并将十六进制字符串填充到有效的ObjectId var\u id=new objectadmin+random/1000.toString16+0000000000000000 //然后查询单个文档: var randomDoc=db.collection.find{{u id:{$gte:{u id} .sort{u id:1}.limit1.toArray[0]; 这是shell表示中的一般逻辑,并且易于调整

因此,在以下几点:

在集合中查找最小和最大主键值

生成介于这些文档的时间戳之间的随机数

将随机数添加到最小值,并找到大于或等于该值的第一个文档


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

从MongoDB的3.2版本开始,您可以使用聚合管道操作符从集合中获取N个随机文档:

//从mycoll集合中随机获取一个文档。 聚合[{$sample:{size:1}] 如果要从集合的筛选子集中选择随机文档,请在管道前添加$match阶段:

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])
如注释中所述,当大小大于1时,返回的文档样本中可能存在重复项。

要获得确定数量的无重复的随机文档,请执行以下操作:

首先获取所有ID 获取文档的大小 循环获取随机索引并跳过重复

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

现在您可以使用聚合。 例如:


.

使用Python pymongo,聚合函数也可以工作

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

这种方法比运行随机数查询要快得多,例如collection.find[random_int]。这对于大型收藏尤其如此

我的PHP/MongoDB随机排序/排序解决方案。希望这对任何人都有帮助

注意:我的MongoDB集合中有数字ID,它引用MySQL数据库记录

首先,我创建一个包含10个随机生成的数字的数组

    $randomNumbers = [];
    for($i = 0; $i < 10; $i++){
        $randomNumbers[] = rand(0,1000);
    }
之后,您可以使用排序管道

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];
您还可以在执行查询后使用shuffle数组

var shuffle=require'shuffle-array'

Accounts.findqry、functionerr、results\u数组{
newIndexArr=shuffleresults_array;

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

db.docs.ensureIndex( { random_point: '2d' } )
db.users.aggregate [{$sample:{size:3}}]

MongoDB现在已经成功了


要选择n个非重复项,请使用{$addFields:{$rand:{}}}进行聚合,然后使用$sort by{$rand:{}}和$limit n进行排序。

在Mongoose中,最好的方法是使用$sample进行聚合调用。 但是,Mongoose不会将Mongoose文档应用于聚合,尤其是在也要应用填充的情况下

要从数据库中获取精简阵列,请执行以下操作:

/*
Sample model should be init first
const Sample = mongoose …
*/

const samples = await Sample.aggregate([
  { $match: {} },
  { $sample: { size: 33 } },
]).exec();
console.log(samples); //a lean Array
要获取mongoose文档数组,请执行以下操作:

db.docs.save( { key : 1, ..., random : Math.random() } )
const samples = (
  await Sample.aggregate([
    { $match: {} },
    { $sample: { size: 27 } },
    { $project: { _id: 1 } },
  ]).exec()
).map(v => v._id);

const mongooseSamples = await Sample.find({ _id: { $in: samples } });

console.log(mongooseSamples); //an Array of mongoose documents

不幸的是,skip的效率相当低,因为它必须扫描那么多文档。此外,如果在获取计数和运行查询之间删除行,则存在竞争条件。请注意,随机数应介于0和独占计数之间。也就是说,如果有10个项目,则随机数应介于0和9之间。否则,光标会移动可以尝试跳过最后一项,但不会返回任何内容。谢谢,这对我来说非常有效。@mstearn,您对效率和竞争条件的评论都是有效的,但对于不删除记录的集合中一次性服务器端批处理提取都不重要的集合,这远远优于黑客IMOMongo Cookbook中的解决方案。将限制设置为-1有什么作用?@MonkeyBonkey如果numberToReturn为0,数据库将使用默认的返回大小。如果数字为负数,则数据库将返回该数字并关闭光标。当您可以选择其他随机键时,为什么要更新数据库?您可能没有要更新的键列表从中随机选择。因此,您每次都必须对整个集合进行排序?而那些随机数较大的不走运记录呢?它们将永远不会被选中。您必须这样做,因为其他解决方案,特别是MongoDB手册中建议的解决方案不起作用。如果第一次查找失败,第二次查找总是返回项目wi最小的随机值。如果你对随机值进行索引,第一个查询总是返回随机数最大的项。在每个文档中添加一个字段?我认为这是不可取的。下面是一个简单的方法,可以将随机字段添加到集合中的每个文档中。函数setRandom{db.topics.find.forEachfunction obj{obj.random=Math.random;db.topics.saveobj;};}db.evalsetRandom;这会随机选择一个文档,但如果您多次这样做,则查找是不独立的。与随机概率所指示的情况相比,您很可能一行获得同一文档两次。这看起来像是循环哈希的一个糟糕实现。它甚至比lacker所说的更糟:即使是一次查找也会有偏差,因为成员不是均匀分布的。要正确地做到这一点,你需要一组,比如说,每个文档10个随机数。每个文档使用的随机数越多,输出分布就越均匀。MongoDB JIRA票证仍然有效:如果你想要该功能,就去评论和投票。注意上面提到的警告类型。这不起作用k有效地处理少量文档。给定两个随机键为3和63的项。在$gte为第一个的位置,将更频繁地选择文档63。在这种情况下,替代解决方案会更有效。我喜欢这个答案,它是我见过的最有效的答案,不需要对服务器端进行大量的混乱。这也偏向于wards文档恰好在其附近有几个点。这是真的,还有其他问题:文档在其随机键上有很强的相关性,因此,如果选择多个文档,可以高度预测哪些文档将作为一个组返回。此外,接近边界0和1的文档不太可能被选择n、 后者可以通过使用环绕在边缘的球形几何图来解决。但是,你应该把这个答案看作是烹饪书配方的改进版本,而不是一个完美的随机选择机制。对于大多数目的来说,它是随机的。@NicodePoel,我喜欢你的答案

回答以及你的评论!我有几个问题要问你:1-你怎么知道接近边界0和1的点不太可能被选择,这是基于一些数学基础吗?2-你能详细介绍一下球面几何地图,它如何更好地进行随机选择,以及如何在MongoDB中进行选择吗。。。谢谢!赞赏你的想法。最后,我有一个很棒的代码,它对CPU和RAM非常友好!感谢您进行完整集合扫描以返回1个元素。。。这一定是效率最低的技术。诀窍在于,它是返回任意数量随机元素的通用解决方案-在这种情况下,当获得>2个随机元素时,它将比其他解决方案更快。另请参见此。考虑对结果集进行随机排序是这个问题的一个更一般的版本——更强大、更有用。这个问题不断出现。最新信息可能会在MongoDB票证跟踪器中的中找到。如果本机实现,它可能是最有效的选择。如果你想要这个功能,去投票吧。这是一个分片的集合吗?下面@johnyhk给出了正确的答案:db.mycoll.aggregate{$sample:{size:1}}有人知道这比只录制第一张记录要慢多少吗?我在争论是否值得采取随机抽样来做某件事,而不是按顺序做。如果你收集的第一条记录的随机字段值相对较高,那么它不会几乎一直被返回吗?thehaitus是正确的,它会-它不适合任何用途此解决方案是完全错误的,假设在0和2^32-1之间添加一个随机数并不能保证任何良好的分布,而使用$gte会使情况变得更糟,因为您的随机选择甚至不会接近伪随机数。我建议永远不要使用这个概念。有什么想法在PHP中会是什么样子吗?或者至少你在上面用过什么语言?是Python吗?这是非常低效的,因为它会将整个集合读入一个数组,然后选择一条记录。好的,也许效率很低,但肯定很方便。如果您的数据大小不太大,请尝试此方法,但最初的问题是针对包含1亿个文档的集合,因此对于这种情况,这将是一个非常糟糕的解决方案!您指定的是语言,而不是正在使用的库?仅供参考,如果在第一行和第三行之间删除文档,则此处存在竞争条件。另外,find+skip非常糟糕,您返回所有文档只是为了选择一个:S。值得注意的是,在内部,这将使用skip和limit,就像许多其他答案一样。您的答案是正确的。但是,请将count替换为估计的\u document\u count,因为count在Mongdo v4.2中已被弃用。可以很容易地扭曲随机日期以说明超线性数据库增长。这是非常大的集合的最佳方法,它适用于O1,这里的其他解决方案中使用的取消行跳过或计数这是一个好方法,但请记住,这并不保证示例中没有相同对象的副本。@MatheusAraujo这并不重要,如果您想要一条记录,但无论如何都很好。不要太迂腐,但问题没有指定MongoDB版本,因此,我认为拥有最新版本是合理的。@Nepoxx请参阅所涉及的处理。@brycejl如果$sample stage没有选择任何匹配的文档,那么它将具有不匹配任何内容的致命缺陷。注意:$sample可能不止一次获得相同的文档,因为我的集合有3000000行。这是唯一有效的解决方案,而且速度足够快。