【问题标题】:MongoDB: how to find 10 random document in a collection of 100?MongoDB:如何在 100 个集合中找到 10 个随机文档?
【发布时间】:2014-09-08 12:18:05
【问题描述】:

MongoDB 是否能够在不进行多次查询的情况下资助大量随机文档?

例如我在加载集合中的所有文档后在 JS 端实现,这很浪费 - 因此只想检查是否可以通过一个 db 查询更好地完成?

我在JS这边走的路:

  • 获取所有数据
  • 制作一个 ID 数组
  • 洗牌 ID 数组(随机顺序)
  • 将数组拼接到所需的文档数
  • 通过按我们在前两次操作后留下的 ID 选择文档来创建文档列表,从整个集合中逐一选择

两个主要缺点是我正在加载所有数据 - 或者我进行了多个查询。

非常感谢任何建议

【问题讨论】:

  • 真的只有 100 份的 10 份文件吗?如果是这样,那么如果当前的解决方案有效,为什么还要优化呢?
  • 嗯,这只是一个例子,我希望集合增长到1000s

标签: javascript mongodb random restangular mlab


【解决方案1】:

很久以前就回答了这个问题,从那时起,MongoDB 有了很大的发展。

正如在另一个答案中发布的那样,MongoDB 现在支持 sampling within the Aggregation Framework,因为版本 3.2:

你可以这样做:

db.products.aggregate([{$sample: {size: 5}}]); // You want to get 5 docs

或者:

db.products.aggregate([
  {$match: {category:"Electronic Devices"}}, // filter the results
  {$sample: {size: 5}} // You want to get 5 docs
]);

不过,关于 $sample 操作符有some warnings

(截至 2017 年 11 月 6 日,最新版本为 3.4)=> 如果不满足任何条件:

  • $sample 是流水线的第一阶段
  • N 小于集合中文档总数的 5%
  • 集合包含 100 多个文档

如果上述任何一个条件不满足,$sample 将执行 集合扫描,然后随机排序以选择 N 个文档。

就像上一个例子中的 $match

老答案

你总是可以跑:

db.products.find({category:"Electronic Devices"}).skip(Math.random()*YOUR_COLLECTION_SIZE)

但顺序不会是随机的,您将需要两次查询(一次计数以获取 YOUR_COLLECTION_SIZE)或估计它有多大(大约 100 条记录,大约 1000 条,大约 10000 条......)

您还可以使用随机数向所有文档添加一个字段并按该数字进行查询。这里的缺点是每次运行相同的查询时都会得到相同的结果。要解决此问题,您始终可以使用限制和跳过甚至排序。您也可以在每次获取记录时更新这些随机数(意味着更多查询)。

--我不知道你是在使用Mongoose、Mondoid还是直接使用Mongo Driver来实现任何特定语言,所以我会写关于mongo shell的所有内容。

因此,假设您的产品记录如下所示:

{
 _id: ObjectId("..."),
 name: "Awesome Product",
 category: "Electronic Devices",
}

我建议使用:

{
 _id: ObjectId("..."),
 name: "Awesome Product",
 category: "Electronic Devices",
 _random_sample: Math.random()
}

那么你可以这样做:

db.products.find({category:"Electronic Devices",_random_sample:{$gte:Math.random()}})

然后,您可以定期运行,以便定期更新文档的 _random_sample 字段:

var your_query = {} //it would impact in your performance if there are a lot of records
your_query = {category: "Electronic Devices"} //Update 
//upsert = false, multi = true
db.products.update(your_query,{$set:{_random_sample::Math.random()}},false,true)

或者只是每当您检索一些记录时,您可以更新所有记录或仅更新一些记录(取决于您检索到的记录数)

for(var i = 0; i < records.length; i++){
   var query = {_id: records[i]._id};
   //upsert = false, multi = false
   db.products.update(query,{$set:{_random_sample::Math.random()}},false,false);
}

编辑

请注意

db.products.update(your_query,{$set:{_random_sample::Math.random()}},false,true)

不会很好地工作,因为它会使用相同的随机数更新与您的查询匹配的所有产品。最后一种方法效果更好(在检索某些文档时更新它们)

【讨论】:

    【解决方案2】:

    从 3.2 开始,有一种更简单的方法可以从集合中获取随机文档样本:

    $样本 3.2 版中的新功能。

    从其输入中随机选择指定数量的文档。

    $sample 阶段的语法如下:

    { $sample: { size: &lt;positive integer&gt; } }

    Source: MongoDB Docs

    在这种情况下:

    db.products.aggregate([{$sample: {size: 10}}]);
    

    【讨论】:

    • 请注意,使用此方法可能会在响应中返回重复的文档。小心!
    【解决方案3】:

    这是我最后想到的:

    var numberOfItems = 10;
    
    
    // GET LIST OF ALL ID's
    SchemaNameHere.find({}, { '_id': 1 }, function(err, data) {
    
        if (err) res.send(err);
    
        // shuffle array, as per here  https://github.com/coolaj86/knuth-shuffle
        var arr = shuffle(data.slice(0));
    
        // get only the first numberOfItems of the shuffled array
        arr.splice(numberOfItems, arr.length - numberOfItems);
    
        // new array to store all items
        var return_arr = [];
    
        // use async each, as per here http://justinklemm.com/node-js-async-tutorial/
        async.each(arr, function(item, callback) {
    
            // get items 1 by 1 and add to the return_arr
            SchemaNameHere.findById(item._id, function(err, data) {
    
                if (err) res.send(err);
                return_arr.push(data);
    
                // go to the next one item, or to the next function if done
                callback();
    
            });
    
        }, function(err) {
    
            // run this when looped through all items in arr
            res.json(return_arr);
    
        });
    
    });
    

    【讨论】:

      【解决方案4】:

      skip 不适合我。这是我的结论:

      var randomDoc = db.getCollection("collectionName").aggregate([ {
          $match : {
      // criteria to filter matches
          }
      }, {
          $sample : {
              size : 1
          }
      } ]).result[0];
      

      获取单个随机结果,匹配条件。

      【讨论】:

        【解决方案5】:

        示例可能不是最好的,因为您不会像那样获得虚拟。 相反,请在后端创建一个对结果进行洗牌的函数。 然后返回打乱后的数组而不是mongodb结果

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-11
          • 2017-04-16
          • 2015-08-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多