【问题标题】:Get random document from a Meteor collection从 Meteor 集合中获取随机文档
【发布时间】:2025-12-02 14:20:04
【问题描述】:

如果没有数字索引,从 Meteor 集合中获取随机文档的最有效算法是什么?

There is another question 使用 skip 方法在 MongoDB 中处理此操作,但这似乎在 Meteor 中不受支持)。

我想出的低效方法是选择所有记录并迭代到一个随机数,但随着集合大小的增长,这显然变得昂贵和麻烦。

【问题讨论】:

  • 来自整个集合的随机文档,还是来自已发布到客户端的集合?
  • 来自整个集合,在服务器端
  • 是的,实际上重复了不止一次。
  • 不是重复的。如前所述,这个问题专门针对 Meteor,它没有 skip 方法

标签: mongodb meteor


【解决方案1】:

遇到了同样的问题,但我需要从查询结果中获取一个随机元素。 由于这个问题提到了 fetch(),我找到了一个解决方案: Meteor: Finding an object from a collection by _id

您可以使用此方法将查询转换为数组。因此将查询结果转换为数组将是Collection.find().fetch()。然后,您可以简单地获取该数组的长度并使用它生成一个随机数,然后选择该数组的元素。

var array = Collection.find().fetch();
var randomIndex = Math.floor( Math.random() * array.length );
var element = array[randomIndex];

注意:这适用于 Meteor,而不是普通的 MongoDB!对于 MongoDB,请参阅使用 skip() 的其他答案或链接问题。

【讨论】:

  • 它有效,但是对于许多文档,此解决方案与问题中提到的解决方案一样糟糕。
【解决方案2】:

目前MongoDB查询语言没有random-operator(虽然有an open feature-request ticket for that)。

更新版本 3.2:您现在可以使用 $sample 聚合运算符来获取随机样本。

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

如果您不能或不想使用它,有一些变通方法,但它们并不漂亮。

一种是使用db.collection.count()获取集合中的文档数。然后你可以用db.collection.find().skip(n).limit(1) 选择n-th 文档。但是当集合很大时,这可能需要一段时间,因为需要使用游标来迭代整个集合。

另一种方法是在插入每个文档时添加一个字段rand,其中包含0.0 到1.0 之间的随机浮点数。然后您可以生成另一个随机数r 并执行db.collection.find({rand:{$gt:r}}).sort({rand:1}).limit(1) 以获得下一个更大的随机数。当您在rand 字段上有索引时,这将非常快。但是随机性不会是均匀分布的,因为那些恰好与它们的前任有较大差距的文档会被更频繁地挑选出来。此外,当r 恰好大于集合中的最大值时,不会返回任何结果。在这种情况下,您应该使用相同的号码再次尝试,但这次使用rand:{$lte:r}sort({rand:-1})。如果这也不返回文档,则集合为空(或至少没有带有 rand 字段的文档)。

您可以快速且公平地选择随机文档的唯一极端情况是您的集合不更改(或至少不经常更改)。在这种情况下,您可以使用从 0 开始的连续整数对所有文档进行编号,对该字段进行索引,并在 find() 中获取介于 0 和已知文档数之间的随机数。

【讨论】:

  • 非常有趣的方法。想必你也可以对周围的所有记录进行计数,然后使用从 0 到 n 的数字方法,随机选择一个。每次写入都需要更新计数,但是对于非常大的集合可能值得付出努力
【解决方案3】:

使用下划线,以下对我有用:

function(){
    var random = _.sample(Collection.find().fetch());
    return Collection.find({_id: random && random._id});
}

【讨论】:

  • 也可以fetched = _.flatten(_.sample(Collection.find().fetch(), 1))
  • 我正在寻找一个随机排序的集合,这条评论帮助我找到了非常有帮助的下划线 shuffle:*.com/questions/27061280/…
  • 为什么要写第二行?第一行本身返回随机集合项,不是吗?
【解决方案4】:

灵感来自@dillygirl 的回复。 如何从Meteor.users 集合中选择N random users。我创建了getRandomBots() 方法(测试twitter API):

function getRandomBots(numberOfBots){
    var usersCollectionArray = Meteor.users.find().fetch();
    //if you want all the users just getRandomBots();
    if(typeof numberOfBots === "undefined"){
        return usersCollectionArray;
    }
    /***
     * RandomNumbers
     * @param numberOfBots
     * @param max
     * @returns {Array}
     */
    function randomNumbers(numberOfBots, max){
        var arr = []
        while(arr.length < numberOfBots){
            var randomnumber=Math.ceil(Math.random()*max);
            var found=false;
            for(var i=0;i<arr.length;i++){
                if(arr[i]==randomnumber){found=true;break}
            }
            if(!found)arr[arr.length]=randomnumber;
        }
        return arr;
    }
    //length of the users collection
    var count = Meteor.users.find().count();

    //random numbers between 0 and Max bots, selecting the number of bots required
    var numbers = randomNumbers(numberOfBots,count);

    //the bots we are gonna select
    var bots = [];

    //pushing users with using the random number as index.
    _.each(numbers,function(item){
        bots.push(usersCollectionArray[item]);
    });

    //testing on server console ...
    _.each(bots,function(item){
       console.log(item.services.twitter.screenName);
    });
}

//selecting 8 bots
getRandomBots(8);

【讨论】:

    【解决方案5】:

    你可以使用 out collections 的随机 _id 属性来解决这个问题,它会在第一次调用时是随机的。由于“选择”,它也可以按不同的顺序排列,但它不是完全随机的,虽然解决了我的任务:

    collection.find({},{sort: _id:Random.choice([1,-1])}})
    

    “真正的”随机数是:

    var items = collection.find({}).fetch();
    var random_items = _.shuffle(items);
    var random_items_id = _.map(random_items,function(element){return element._id});
    return collection.find({_id:{$in:random_items_id}});
    

    【讨论】:

      【解决方案6】:
      import { Random } from 'meteor/random';
      
      const getRandomDocuments = function(amount) {
          // finds the next _id >= from a random one
          const randomId = Random.id();
          const gteRandom = MyCollection.find({
              _id: { $gte: randomId }
          }, {
              fields: { _id: 1 },
              sort: [
                  ['_id', 'asc']
              ],
              limit: amount
          });
          const remainingToGet = Math.max(0, amount - gteRandom.count());
          // if it didn't find enough looks for next _id < the random one
          const ltRandom = MyCollection.find({
              _id: { $lt: randomId }
          }, {
              fields: { _id: 1 },
              sort: [
                  ['_id', 'asc']
              ],
              limit: remainingToGet
          });
          // combine the two queries
          let allIds = [];
          gteRandom.forEach(function(doc) {
              allIds.push(doc._id);
          });
          ltRandom.forEach(function(doc) {
              allIds.push(doc._id);
          });
          return MyCollection.find({
              _id: { $in: allIds }
          });
      }
      

      【讨论】: