我想从一个巨大的集合(1亿条记录)中获得一个随机记录。

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

数据已经在那里,没有字段可以生成随机数并获得随机行。


当前回答

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附加到它,然后再次添加它。

其他回答

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

在这里,你不需要花空间存储随机数字的集合。

这工作得很好,它是快速的,适用于多个文档,不需要填充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.如何在mongodb问题中找到随机记录被标记为此问题的副本。不同之处在于,这个问题明确地询问单个记录,而另一个问题明确地询问随机文档。

有效可靠的方法是:

在每个文档中添加一个名为“random”的字段,并为其分配一个随机值,为该随机字段添加一个索引,如下所示:

让我们假设我们有一个名为“links”的网络链接集合,我们想从它中随机链接:

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

为了确保同一个链接不会第二次弹出,用一个新的随机数更新它的随机场:

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

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

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

在Mongoose中最好的方法是使用$sample进行聚合调用。 然而,Mongoose并不会将Mongoose文档应用到Aggregation上——尤其是当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