【问题标题】:Random record from MongoDB来自 MongoDB 的随机记录
【发布时间】:2010-05-13 02:43:16
【问题描述】:

我希望从庞大的集合(1 亿条记录)中获取随机记录。

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

数据已经存在,没有可以生成随机数并获取随机行的字段。

【问题讨论】:

  • 另见SO question titled "Ordering a result set randomly in mongo"。考虑随机排序结果集是这个问题的一个更通用的版本——更强大、更有用。
  • 这个问题不断出现。最新信息可能在 MongoDB 票证跟踪器中的 feature request to get random items from a collection 中找到。如果本地实现,它可能是最有效的选择。 (如果你想要这个功能,就去投票吧。)
  • 这是一个分片集合吗?
  • 下面@JohnnyHK 给出了正确答案:db.mycoll.aggregate( { $sample: { size: 1 } } )
  • 有谁知道这比只记录第一条记录要慢多少?我正在讨论是否值得随机抽样来做某事而不是按顺序做。

标签: mongodb random mongodb-query


【解决方案1】:

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

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

如果您想从集合的过滤子集中选择随机文档,请将$match 阶段添加到管道:

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

如 cmets 中所述,当 size 大于 1 时,返回的文档样本中可能存在重复。

【讨论】:

  • 这是一个好方法,但请记住,它不能保证样本中没有相同对象的副本。
  • @MatheusAraujo 如果你想要一张唱片也没关系,但无论如何都是好点
  • 不要迂腐,但问题没有指定 MongoDB 版本,所以我认为拥有最新版本是合理的。
  • @Nepoxx 请参阅the docs 了解所涉及的处理。
  • @brycejl 如果 $sample 阶段没有选择任何匹配的文档,那将存在不匹配任何内容的致命缺陷。
【解决方案2】:

对所有记录做个计数,生成一个介于0和计数之间的随机数,然后做:

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

【讨论】:

  • 不幸的是,skip() 效率很低,因为它必须扫描那么多文档。此外,如果在获取计数和运行查询之间删除行,则存在竞争条件。
  • 注意随机数应该在0到计数之间(不包括)。即,如果您有 10 项,则随机数应介于 0 和 9 之间。否则光标可能会尝试跳过最后一项,并且不会返回任何内容。
  • 谢谢,非常适合我的目的。 @mstearn,您的 cmets 在效率和竞争条件上都是有效的,但是对于两者都不重要的集合(在不删除记录的集合中一次性服务器端批量提取),这大大优于 hacky(IMO) Mongo Cookbook 中的解决方案。
  • 将限制设置为 -1 有什么作用?
  • @MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "如果 numberToReturn 为 0,则数据库将使用默认返回大小。如果数字为负数,则数据库将返回该数字并关闭游标。"
【解决方案3】:

MongoDB 3.2 更新

3.2 将$sample 引入聚合管道。

还有一个很好的 blog post 可以将其付诸实践。

对于旧版本(上一个答案)

这实际上是一个功能请求: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 附加到它,然后再次添加它。

【讨论】:

  • 这里有一个简单的方法可以将随机字段添加到集合中的每个文档中。函数 setRandom() { db.topics.find().forEach(函数 (obj) {obj.random = Math.random();db.topics.save(obj);}); } db.eval(setRandom);
  • 这会随机选择一个文档,但如果您多次这样做,则查找不是独立的。与随机机会相比,您更有可能连续两次获得相同的文档。
  • 看起来循环散列的实现很糟糕。它甚至比缺乏者说的更糟糕:即使是一次查找也是有偏差的,因为随机数不是均匀分布的。要正确执行此操作,您需要一组,例如,每个文档 10 个随机数。每个文档使用的随机数越多,输出分布就越均匀。
  • MongoDB JIRA 票仍然有效:jira.mongodb.org/browse/SERVER-533 如果您想要该功能,请去评论和投票。
  • 注意所提到的警告类型。这不适用于少量文档。给定两个随机键为 3 和 63 的项目。将更频繁地选择文档 #63,其中$gte 是第一个。在这种情况下,替代解决方案 stackoverflow.com/a/9499484/79201 会更好。
【解决方案4】:

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

【讨论】:

  • 我喜欢这个答案,它是我见过的最有效的答案,不需要一堆乱七八糟的服务器端。
  • 这也偏向于在其附近恰好有几个点的文档。
  • 确实如此,还有其他问题:文档在其随机键上具有很强的相关性,因此如果您选择多个文档,则可以高度预测哪些文档将作为一个组返回。此外,接近边界(0 和 1)的文档不太可能被选中。后者可以通过使用环绕在边缘的球形地理映射来解决。但是,您应该将此答案视为食谱食谱的改进版本,而不是完美的随机选择机制。对于大多数目的来说,它是足够随机的。
  • @NicodePoel,我喜欢你的回答和评论!我有几个问题要问你:1- 你怎么知道接近边界 0 和 1 的点不太可能被选择,这是基于一些数学基础吗? 2- 你能详细说明球形地理映射吗?它将如何更好地随机选择,以及如何在 MongoDB 中做到这一点? ...赞赏!
  • 欣赏你的想法。最后,我有一个很棒的代码,它对 CPU 和 RAM 非常友好!谢谢
【解决方案5】:

以下配方比 mongo 食谱解决方案慢一点(在每个文档上添加一个随机键),但返回更均匀分布的随机文档。与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)快得多,并且比 Michael 报告的“cookbook”方法生成更均匀的随机文档:

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

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

  • skip() 方法平均耗时 180 毫秒

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

  • 此方法将随着时间的推移均匀地选取所有元素。

  • 在我的基准测试中,它只比说明书方法慢 30%。

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

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

【讨论】:

    【解决方案6】:

    这是一种使用 _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 值本质上更简单,但基本思想相同。

    【讨论】:

    • 我收集了 300 000 000 行。这是唯一有效且速度足够快的解决方案。
    【解决方案7】:

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

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

    See the doc.

    【讨论】:

    • 注意:$sample 可能会多次获取同一个文档
    【解决方案8】:

    在 Python 中使用 pymongo:

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

    【讨论】:

    • 值得注意的是,在内部,这将使用跳过和限制,就像许多其他答案一样。
    • 你的答案是正确的。但是,请将 count() 替换为 estimated_document_count(),因为 count() 在 Mongdo v4.2 中已弃用。
    【解决方案9】:

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

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

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

    【讨论】:

      【解决方案10】:

      如果没有数据可以关闭,那就很难了。 _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);
      

      【讨论】:

      • 任何想法在 PHP 中会是什么样子?或者至少您在上面使用了什么语言?是 Python 吗?
      【解决方案11】:

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

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

      【讨论】:

      • 很容易扭曲随机日期以解释超线性数据库增长。
      • 这是非常大的集合的最佳方法,它适用于 O(1),此处其他解决方案中使用的 unline skip() 或 count()
      【解决方案12】:

      我在 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;
      }
      

      【讨论】:

        【解决方案13】:

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

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

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

        【讨论】:

          【解决方案14】:

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

          如果你想从数据库中精确地选择 m 个文档中的 n 个,你可以这样做:

          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)是数据库中的文档数,“countSubset”(n)是要检索的文档数。

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

          【讨论】:

          • 进行完整的集合扫描以返回 1 个元素...这一定是效率最低的技术。
          • 诀窍在于,它是返回任意数量随机元素的通用解决方案 - 在这种情况下,当获取 > 2 个随机元素时,它会比其他解决方案更快。
          【解决方案15】:

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

          在这里你不需要花费空间来存储随机数。

          【讨论】:

            【解决方案16】:

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

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

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

            【讨论】:

            • 如果你的集合中第一条记录的random_field值比较高,是不是几乎一直都返回?
            • thehaitus 是正确的,它会——它不适合任何目的
            • 这个解决方案是完全错误的,添加一个随机数(让我们想象在 0 到 2^32-1 之间)并不能保证任何良好的分布,使用 $gte 会使情况变得更糟,因为你随机选择甚至不会接近伪随机数。我建议永远不要使用这个概念。
            【解决方案17】:

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

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

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

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

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

            【讨论】:

              【解决方案18】:

              没有一个解决方案对我很有效。特别是当有很多间隙并且设置很小时。 这对我来说效果很好(在 php 中):

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

              【讨论】:

              • 您指定了语言,但没有指定您正在使用的库?
              • 仅供参考,如果在第一行和第三行之间删除了文档,则此处存在竞争条件。 find + skip 也很糟糕,您返回所有文件只是为了选择一个:S。
              • find() 应该只返回一个游标,因此它不会返回所有实际文档。但是是的,这种妥协在我的测试中损失了 x 1000000 倍的性能;)
              【解决方案19】:

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

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

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

                  $randomNumbers = [];
                  for($i = 0; $i < 10; $i++){
                      $randomNumbers[] = rand(0,1000);
                  }
              

              在我的聚合中,我使用结合了 $arrayElemAt 和 $mod(模数)的 $addField 管道运算符。模数运算符会给我一个 0 - 9 之间的数字,然后我用它从数组中选择一个随机生成的数字。

                  $aggregate[] = [
                      '$addFields' => [
                          'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
                      ],
                  ];
              

              之后就可以使用排序管道了。

                  $aggregate[] = [
                      '$sort' => [
                          'random_sort' => 1
                      ]
                  ];
              

              【讨论】:

                【解决方案20】:

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

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

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

                【讨论】:

                  【解决方案21】:

                  MongoDB 现在有 $rand

                  要选择 n 个不重复的项目,请与 { $addFields: { _f: { $rand: {} } } } 然后 $sort by _f$limit n 聚合。

                  【讨论】:

                  • 请问有什么例子吗?
                  【解决方案22】:

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

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

                  【讨论】:

                    【解决方案23】:

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

                    我已经用 50,000 个文档(过滤器将其减少到大约 30,000 个)测试了这种方法,它在具有 16GB 内存和 SATA3 硬盘的 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

                    400ms 听起来很长,但确实如此,如果您有 5000 万条记录而不是 50000 万条记录,这可能会增加开销,使其在多用户情况下变得无法使用。

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

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

                    【讨论】:

                      【解决方案24】:

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

                      1. 为您的收藏中的 .rand 字段添加索引
                      2. 使用查找和刷新,例如:
                      // 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

                      【讨论】:

                        【解决方案25】:

                        Mongoose 中最好的方法是使用 $sample 进行聚合调用。 但是,Mongoose 不会将 Mongoose 文档应用于聚合 - 特别是在要应用 populate() 时。

                        从数据库中获取“精简”数组:

                        /*
                        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 文档:

                        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
                        

                        【讨论】:

                          【解决方案26】:

                          对我来说,我想以随机顺序获得相同的记录,所以我创建了一个用于排序的空数组,然后生成 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]);
                          }
                          

                          【讨论】:

                            【解决方案27】:

                            如果您使用 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
                            

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

                            【讨论】:

                            • 这非常低效,因为它将整个集合读入一个数组,然后选择一条记录。
                            • 好吧,也许效率低下,但肯定很方便。如果你的数据量不是太大,试试这个
                            • 当然,但最初的问题是针对包含 1 亿个文档的集合,因此对于这种情况,这将是一个非常糟糕的解决方案!
                            【解决方案28】:

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

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

                            Accounts.find(qry,function(err,results_array){ newIndexArr=shuffle(results_array);

                            【讨论】:

                              【解决方案29】:

                              有效且可靠的方法是:

                              为每个文档添加一个名为“random”的字段并为其分配一个随机值,为该随机字段添加一个索引,然后执行以下操作:

                              假设我们有一个称为“链接”的网络链接集合,我们希望从中获得一个随机链接:

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

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

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

                              【讨论】:

                              • 当您可以选择不同的随机密钥时,为什么要更新数据库?
                              • 您可能没有可供随机选择的键列表。
                              • 所以你每次都要对整个集合进行排序?那么得到大随机数的倒霉记录呢?他们永远不会被选中。
                              • 您必须这样做,因为其他解决方案,尤其是 MongoDB 书中建议的解决方案,不起作用。如果第一次查找失败,第二次查找总是返回具有最小随机值的项。如果您按降序排列随机索引,则第一个查询始终返回具有最大随机数的项目。
                              • 在每个文档中添加一个字段?我认为这是不可取的。
                              猜你喜欢
                              • 2016-02-01
                              • 2013-07-14
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2021-01-18
                              • 2018-12-07
                              相关资源
                              最近更新 更多