当前位置:  开发笔记 > 编程语言 > 正文

来自MongoDB的随机记录

如何解决《来自MongoDB的随机记录》经验,为你挑选了11个好方法。

我希望得到一张巨大的(1亿张唱片)的随机唱片mongodb.

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

有什么建议?



1> JohnnyHK..:

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

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])


这是一种好方法,但请记住,它并不保证样本中没有相同对象的副本.
@MatheusAraujo如果你想要一个记录但无论如何都要好,这无关紧要
不要迂腐,但问题没有指定MongoDB版本,所以我假设最新版本是合理的.
@Nepoxx有关所涉及的处理,请参阅[文档](https://docs.mongodb.com/manual/reference/operator/aggregation/sample/#behavior).

2> ceejayoz..:

计算所有记录,生成0到计数之间的随机数,然后执行:

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


不幸的是,skip()效率很低,因为它必须扫描那么多文档.此外,如果在获取计数和运行查询之间删除行,则存在竞争条件.
请注意,随机数应介于0和计数之间(不包括).即,如果你有10个项目,则随机数应该在0到9之间.否则光标可能会尝试跳过最后一个项目,并且不会返回任何内容.
谢谢,完美地为我的目的工作.@mstearn,您对效率和竞争条件的评论都是有效的,但对于既不重要的集合(在记录未被删除的集合中的一次性服务器端批量提取),这远远优于hacky(IMO) Mongo Cookbook中的解决方案.
将限制设置为-1的作用是什么?

3> 小智..:
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到它,然后重新添加即可.


看起来像循环哈希的糟糕实现.它甚至比缺乏更糟糕的说:甚至一个查找是有偏见的,因为随机数不是均匀分布的.要正确地执行此操作,您需要一组,例如,每个文档10个随机数.每个文档使用的随机数越多,输出分布就越均匀.
这会随机选择一个文档,但如果您多次执行此操作,则查找不是独立的.您更有可能连续两次获得相同的文档而不是随机机会.
这是将随机字段添加到集合中每个文档的简单方法.function setRandom(){db.topics.find().forEach(function(obj){obj.random = Math.random(); db.topics.save(obj);}); } db.eval(setRandom);
MongoDB JIRA票仍然存在:https://jira.mongodb.org/browse/SERVER-533如果您需要该功能,请发表评论并投票.

4> Nico de Poel..:

您还可以使用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 )

这只需要一个查询而不需要空检查,而且代码干净,简单且灵活.您甚至可以使用geopoint的Y轴为查询添加第二个随机性维度.


我喜欢这个答案,它是我见过的最有效的一个,不需要在服务器端搞乱一堆.
这是事实,并且还存在其他问题:文档与其随机密钥密切相关,因此如果您选择多个文档,则可以高度预测哪些文档将作为一组返回.此外,不太可能选择接近边界(0和1)的文档.后者可以通过使用球形地理映射来解决,球形地理映射在边缘处缠绕.但是,您应该将此答案视为菜谱配方的改进版本,而不是完美的随机选择机制.对于大多数用途来说,它足够随机.
这也偏向于附近几乎没有点的文档.

5> spam_eggs..:

以下配方比mongo cookbook解决方案稍慢(在每个文档上添加随机键),但返回更均匀分布的随机文档.它比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)方法快得多,并且生成比迈克尔报告的"cookbook"方法更均匀的随机文档:

对于包含1,000,000个元素的集合:

这种方法在我的机器上花费不到一毫秒

skip()方法平均需要180毫秒

食谱方法将导致大量文档永远不会被选中,因为它们的随机数不支持它们.

此方法将随时间均匀地选取所有元素.

在我的基准测试中,它比食谱方法慢了30%.

随机性并非100%完美,但它非常好(必要时可以改进)

这个配方并不完美 - 完美的解决方案将是其他人注意到的内置功能.
然而,它应该是许多目的的妥协.



6> Blakes Seven..:

这是一种使用默认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];

这是shell表示的一般逻辑,易于适应.

所以要点:

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

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

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

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



7> Jabba..:

在Python中使用pymongo:

import random

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


值得注意的是,在内部,这将使用跳过和限制,就像许多其他答案一样.

8> dm...:

如果那里没有关键数据,那就太难了._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);



9> dbam..:

现在您可以使用聚合.例:

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

见文档.


注意:$ sample可能多次获得同一文档

10> Martin Nowak..:

您可以选择随机时间戳并搜索之后创建的第一个对象.它只扫描单个文档,但不一定能为您提供统一的分发.

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



11> 小智..:

使用Python(pymongo),聚合函数也可以使用。

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

这种方法比运行查询随机数(例如collection.find([random_int])的速度快得多。对于大型集合,尤其如此。

推荐阅读
N个小灰流_701
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有