在mongo中随机排序结果集

时间:2011-12-14 06:21:53

标签: mongodb

我最近发现Mongo在命令语法中没有等同于“ORDER BY RAND()”的SQL(https://jira.mongodb.org/browse/SERVER-533)

我在http://cookbook.mongodb.org/patterns/random-attribute/看到了这个建议,坦率地说,在文档中添加随机属性就像是一个黑客。这不起作用,因为这会对我想要随机化的任何给定查询设置隐式限制。

另一个广泛给出的建议是选择一个随机索引来抵消。由于我的文档插入的顺序,这将导致其中一个字符串字段按字母顺序排列,这对我的网站用户来说不会感觉非常随机。

我对如何通过代码解决这个问题有一些想法,但我觉得我错过了一个更明显和原生的解决方案。有没有人对如何更优雅地解决这个问题有一个想法或想法?

6 个答案:

答案 0 :(得分:7)

我必须同意:最简单的方法是在文档中安装随机值。不需要非常大范围的值 - 您选择的数字取决于查询的预期结果大小(对于大多数情况,1,000 - 1,000,000个不同的整数应该足够)。

运行查询时,不要担心随机字段 - 而是将其编入索引并使用它进行排序。由于随机数和文档之间没有对应关系,因此您应该得到相当随机的结果。请注意,冲突可能会导致文档按自然顺序返回。

虽然这个 肯定是一个黑客攻击,但你有一个非常简单的转义路径:鉴于MongoDB的无架构特性,一旦在服务器中支持随机排序,你就可以简单地停止包含随机字段。如果大小是一个问题,您可以运行批处理作业以从现有文档中删除该字段。如果您仔细设计,则不应对客户端代码进行重大更改。

另一种选择是对于给定查询随机化和返回的结果数量进行长时间的考虑。简单地在客户端代码中进行洗牌可能不会太昂贵(例如,如果您只考虑最近的10,000个帖子)。

答案 1 :(得分:2)

如果不选择您提到的两种解决方案中的任何一种,您无法做到。如果您的集合变得比几千个文档更大,那么选择随机偏移是一个可怕的想法。其原因是skip(n)操作花费O(n)时间。换句话说,随机偏移越高,查询所需的时间就越长。

在我看来,在文档中添加一个随机字段是最不具备问题的解决方案,它给出了MongoDB的当前功能集。它提供了稳定的查询时间,并让您对集合的随机化方式有所了解(并允许您在每次查询后通过findAndModify生成新的随机值)。我也不明白这会如何对使用随机化的查询施加隐含限制。

答案 2 :(得分:2)

你可以尝试一下 - 它很快,适用于多个文档,并且不需要在开头填充rand字段,这最终将填充自己:

  1. 将索引添加到集合中的.rand字段
  2. 使用find和refresh,例如:
  3. // 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)
      }
    })
    

答案 3 :(得分:0)

  

另一个广泛给出的建议是选择一个随机索引来抵消。由于我的文档被插入的顺序,这将导致其中一个字符串字段按字母顺序排列,这对我的网站用户来说不会感觉非常随机。

为什么呢?如果您有7.000个文档,并且从0到6999中选择了三个随机偏移量,则所选文档将是随机的,即使集合本身按字母顺序排序也是如此。

答案 4 :(得分:0)

可以插入一个id字段($ id字段因为它不是实际数字而无法工作)使用模数数来获得随机跳过。如果你有10,000条记录并且你想要10个结果,你可以选择1到1000之间的模数,随机取数为253,然后请求mod(id,253)= 0,如果id被索引,这个速度相当快。然后随机排序客户端那10个结果。当然它们是均匀间隔而不是真正随机的,但它接近于所期望的。

答案 5 :(得分:0)

这两个选项对我来说似乎都是非完美的黑客,随机提交并且总是具有相同的值,而skip会返回相同数字的相同记录。

为什么不使用一些随机字段进行排序然后随机跳过,我承认它也是一个黑客,但根据我的经验,可以提供更好的随机性。