【问题标题】:Ordering a result set randomly in mongo在mongo中随机排序结果集
【发布时间】:2012-01-19 23:29:24
【问题描述】:

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

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

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

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

【问题讨论】:

标签: mongodb


【解决方案1】:

这两个选项对我来说似乎都不是完美的 hack,随机归档并且总是具有相同的值,并且 skip 将针对相同的数字返回相同的记录。

您为什么不使用一些随机字段进行排序然后随机跳过,我承认这也是一种 hack,但根据我的经验,它可以更好地理解随机性。

【讨论】:

    【解决方案2】:

    您可以尝试一下 - 它速度快,适用于多个文档,并且不需要在开头填充 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)
      }
    })
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

        虽然这确实是一个 hack,但您有一个非常简单的逃生路线:鉴于 MongoDB 的无模式特性,一旦服务器支持随机排序,您就可以简单地停止包含随机字段.如果大小是一个问题,您可以运行批处理作业以从现有文档中删除该字段。如果您仔细设计,您的客户端代码应该不会有重大变化。

        另一种选择是仔细考虑将为给定查询随机返回的结果数量。简单地在客户端代码中进行改组可能不会过于昂贵(即,如果您只考虑最近的 10,000 个帖子)。

        【讨论】:

        • 是的,在这种情况下,获取整个集合并在客户端代码上完成这一切实际上是合理的,所以这就是我最终要做的。它只是感觉类似于数据存储原生的功能。
        • 我不认为随机性是数据库非常普遍的要求,在恒定时间内高效实现有点棘手。
        • 我还要补充一点,随机排序的分页会变得很奇怪。
        【解决方案5】:

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

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

        【讨论】:

        • 它添加了一个隐式限制,因为在任何随机生成的数字上可能只有一定数量的文档——例如,如果我抽取的随机数是 0.9111,那么只会有一定数量的文档将符合条件 $gte => 0.9111
        • @Andy,这只是符合条件的文件数量的限制,少于您申请所需的数量。如果您遇到这种极端情况,您只需使用新生成的随机数作为新查询来补充您的集合。
        • @Andy,如果您需要您的集合是真正随机的(例如,避免集合由例如,0.9111 与使用 0.9222 返回的结果有 90% 相同)
        【解决方案6】:

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

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

        【讨论】:

        • 您的解决方案确实有效,但需要大量子查询来计算完整集合大小,然后手动提取随机偏移量。如果我想以随机顺序提取大量记录,这肯定很麻烦。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-31
        • 2020-10-03
        • 2022-06-15
        • 1970-01-01
        • 1970-01-01
        • 2019-12-29
        相关资源
        最近更新 更多