首页 文章

来自MongoDB的随机记录

提问于
浏览
270

我希望得到一张巨大的(1亿条记录) mongodb 的随机记录 .

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

有什么建议?

24 回答

  • 1

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

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

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

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

    你可以随机选择_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)
                })
            })
        })
    

    在这里,你不需要花费空间来存储集合中的随机数 .

  • 0

    如果您正在使用文档到对象包装器mongoid,则可以在Ruby中执行以下操作 . (假设您的模型是用户)

    User.all.to_a[rand(User.count)]
    

    在我的.irbrc中,我有

    def rando klass
        klass.all.to_a[rand(klass.count)]
    end
    

    所以在rails控制台中,我可以做,例如,

    rando User
    rando Article
    

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

  • 54

    当我面对类似的解决方案时,我回溯并发现业务请求实际上是为了创建呈现的库存的某种形式的轮换 . 在这种情况下,有更好的选择,其中包括像Solr这样的搜索引擎,而不是像MongoDB这样的数据存储 .

    简而言之,随着“智能旋转”内容的要求,我们应该做的而不是所有文档中的随机数是包括个人q分数修饰符 . 为了自己实现这一点,假设用户数量很少,您可以为每个用户存储一个文档,其中包含productId,展示次数,点击次数,上次查看日期以及企业发现的有意义的其他因素来计算aq得分修改 . 检索要显示的集合时,通常从数据存储中请求的文档多于最终用户请求的数据,然后应用q score修饰符,获取最终用户请求的记录数,然后随机化结果页面,一个小的设置,所以只需在应用程序层(内存中)对文档进行排序 .

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

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

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

  • 82

    在Python中使用pymongo:

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

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

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

    这种方法比运行随机数的查询(例如collection.find([random_int])更为明确 . 对于大型集合尤其如此 .

  • 5

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

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

    您还可以使用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轴为查询添加第二个随机性维度 .

  • 6

    如果您使用的是猫鼬,那么您可以使用mongoose-random mongoose-random

  • 1

    这是有效和可靠的:

    在每个文档中添加一个名为“random”的字段,并为其分配一个随机值,为随机字段添加索引并按如下方式继续:

    假设我们有一个名为“链接”的网络链接集合,我们想要一个随机链接:

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

    要确保第二次不会弹出相同的链接,请使用新的随机数更新其随机字段:

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

    My solution on 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;
    }
    
  • 2

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

    • 首先获取所有ID

    • 获取文件大小

    • 循环geting随机索引并跳过重复

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

    使用Map / Reduce,您当然可以获得随机记录,但不一定非常有效,具体取决于您最终使用的最终过滤集合的大小 .

    我已经用50,000个文档测试了这个方法(过滤器将它减少到大约30,000个),并且在带有16GB RAM和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 .

    400毫秒听起来很长一段时间,实际上,如果你有五千万条记录而不是五万条,这可能会增加开销,使其在多用户情况下无法使用 .

    MongoDB有一个未解决的问题,即在核心中包含此功能... https://jira.mongodb.org/browse/SERVER-533

    如果将这种“随机”选择内置到索引查找中,而不是将id收集到数组中然后选择一,这将有助于令人难以置信 . (去投票吧!)

  • 1

    以下配方比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);
    

    Benchmark results

    这个方法比 skip() 方法(ceejayoz)快得多,并且生成比迈克尔报告的"cookbook"方法更均匀的随机文档:

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

    • 此方法在我的机器上花费不到一毫秒

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

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

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

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

    • 随机性不是100%完美但它非常好(如果需要可以改进)

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

  • 115

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

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

    See the doc .

  • 3

    这很好用,它需要填充 rand 字段,最终填充自己:

    • 将索引添加到集合中的.rand字段

    • 使用查找和刷新,类似于:

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

    PS . How to find random records in mongodb问题被标记为此问题的副本 . 区别在于这个问题明确地要求单个记录作为另一个明确地关于获取随机文档 s .

  • 7

    没有解决方案适合我 . 特别是当有很多空隙而且设置很小时 . 这对我来说非常好(在PHP中):

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

    我建议使用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也应该可以在分片数据库上使用 .

    如果要从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)是要检索的文档数 .

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

  • 2

    MongoDB 3.2更新

    3.2将$sample引入聚合管道 .

    将它付诸实践还有一个很好的blog post .

    适用于旧版本(上一个答案)

    这实际上是一个功能请求:http://jira.mongodb.org/browse/SERVER-533但它是在"Won'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 } } )
    }
    

    查找具有最接近 rand 的随机数的文档需要使用 $gte$lte 进行查询 .

    当然,您需要在随机字段上编制索引:

    db.docs.ensureIndex( { key : 1, random :1 } )
    

    如果您已经查询了索引,只需将其删除,将 random: 1 追加到它,然后重新添加即可 .

  • -7

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

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

    这是一种使用 _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表示的一般逻辑,易于适应 .

    所以要点:

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

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

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

    这使用"hex"中的时间戳值"padding"来形成有效的 ObjectId 值,因为这是我们正在寻找的 . 使用整数作为 _id 值本质上更简单,但在点上基本相同 .

  • 2

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

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

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

    选择随机文件 . 请确保您使用ensureIndex({random_field:1})

相关问题