Заказ случайного набора результатов в монго

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

Оба варианта кажутся мне не идеальными хаками, случайными и всегда будут иметь одинаковое значение, а skip вернет те же записи для одного и того же номера.

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