Порядок набора результатов случайным образом в монго - PullRequest
17 голосов
/ 14 декабря 2011

Я недавно обнаружил, что Mongo не имеет SQL-эквивалента "ORDER BY RAND ()" в синтаксисе команды (https://jira.mongodb.org/browse/SERVER-533)

Я видел рекомендацию на http://cookbook.mongodb.org/patterns/random-attribute/ и, честно говоря, добавивслучайный атрибут документа воспринимается как хак. Это не сработает, потому что это накладывает неявное ограничение на любой запрос, который я хочу рандомизировать.

Другим широко распространенным предложением является выбор случайного индекса для смещенияfrom. Из-за порядка, в который были вставлены мои документы, это приведет к тому, что одно из строковых полей будет в алфавитном порядке, что не будет восприниматься пользователем моего сайта очень случайным.

У меня есть пара идейо том, как я могу решить это с помощью кода, но я чувствую, что мне не хватает более очевидного и нативного решения. У кого-нибудь есть мысли или идеи о том, как решить это более элегантно?

Ответы [ 6 ]

7 голосов
/ 14 декабря 2011

Я должен согласиться: проще всего установить случайное значение в ваши документы.Также необязательно должен быть чрезвычайно большой диапазон значений - число, которое вы выбираете, зависит от ожидаемого размера результата для ваших запросов (для большинства случаев должно быть достаточно 1000–1000 000 различных целых чисел).

Когда выЗапустите запрос, не беспокойтесь о случайном поле - вместо этого индексируйте его и используйте для сортировки.Поскольку между случайным числом и документом нет соответствия, вы должны получить довольно случайные результаты.Обратите внимание, что из-за коллизий документы могут быть возвращены в естественном порядке.

Хотя этот , безусловно, является хаком, у вас есть очень простой путь к спасению: учитывая отсутствие схемы в MongoDB, вы можетепросто перестаньте включать случайное поле, как только на сервере появится поддержка случайной сортировки.Если размер является проблемой, вы можете запустить пакетное задание, чтобы удалить поле из существующих документов.Не должно быть существенных изменений в вашем клиентском коде, если вы тщательно его спроектируете.

Альтернативный вариант - долго и усердно думать о количестве результатов, которые будут рандомизированы и возвращены для данного запроса.Это может быть не слишком дорого - просто перетасовать код клиента (т. Е. Если учесть только самые последние 10000 сообщений).

2 голосов
/ 20 ноября 2014

Вы можете попробовать это - это быстро, работает с несколькими документами и не требует заполнения поля 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)
  }
})
2 голосов
/ 14 декабря 2011

То, что вы хотите, не может быть сделано без выбора одного из двух упомянутых вами решений. Выбор случайного смещения - ужасная идея, если ваша коллекция становится больше нескольких тысяч документов. Причина этого заключается в том, что операция skip (n) занимает O (n) времени. Другими словами, чем выше ваше случайное смещение, тем больше времени займет запрос.

Добавление рандомизированного поля к документу, на мой взгляд, является наименее хакерским решением, учитывая текущий набор функций MongoDB. Он обеспечивает стабильное время запросов и дает вам некоторое представление о том, как коллекция рандомизируется (и позволяет вам генерировать новое случайное значение после каждого запроса, например, с помощью findAndModify). Я также не понимаю, как это наложит неявное ограничение на ваши запросы, использующие рандомизацию.

0 голосов
/ 11 июня 2015

Обе опции кажутся мне не идеальными взломами, случайное поле и всегда будет иметь одинаковое значение, а пропуск будет возвращать одинаковые записи для одного и того же числа.чтобы отсортировать, а затем пропустить случайно, я признаю, что это тоже взлом, но по моему опыту дает лучшее чувство случайности.

0 голосов
/ 25 января 2013

Можно вставить поле id (поле $ id не будет работать, потому что оно не является действительным числом), используя математический модуль для получения случайного пропуска.Если у вас 10 000 записей и вы хотите 10 результатов, вы можете выбрать модуль от 1 до 1000 случайным образом, например, как 253, а затем запросить, где mod (id, 253) = 0, и это достаточно быстро, если индексируется id.Затем случайным образом отсортируйте на стороне клиента эти 10 результатов.Конечно, они равномерно распределены, а не случайны, но это близко к желаемому.

0 голосов
/ 14 декабря 2011

Другим широко распространенным предложением является выбор случайного индекса для смещения.Из-за порядка, в который были вставлены мои документы, это приведет к тому, что одно из строковых полей будет в алфавитном порядке, что не будет восприниматься пользователем моего сайта очень случайно.

Почему?Если у вас есть 7.000 документов, и вы выбираете три случайных смещения от 0 до 6999, выбранные документы будут случайными, даже если сама коллекция отсортирована по алфавиту.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...