Случайная запись из MongoDB

Я хочу получить случайную запись из огромной (100 миллионов записей) mongodb.

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

Любые предложения?

Ответ 1

Начиная с версии MongoDB 3.2, вы можете получить N случайных документов из коллекции, используя оператор конвейера агрегации $sample:

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

Если вы хотите выбрать случайный документ из отфильтрованного подмножества коллекции, добавьте этап $match к конвейеру:

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

Как отмечено в комментариях, когда size больше 1, в возвращенном образце документа могут быть дубликаты.

Ответ 2

Сделайте счетчик всех записей, создайте случайное число между 0 и счетчиком, а затем выполните:

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

Ответ 3

Обновление для 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 к нему и добавьте его снова.

Ответ 4

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

Сначала включите геопространственную индексацию в коллекции:

db.docs.ensureIndex( { random_point: '2d' } )

Чтобы создать связку документов со случайными точками по оси X:

for ( i = 0; i < 10; ++i ) {
    db.docs.insert( { key: i, random_point: [Math.random(), 0] } );
}

Затем вы можете получить случайный документ из коллекции следующим образом:

db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )

Или вы можете получить несколько документов, ближайших к случайной точке:

db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

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

Ответ 5

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

function draw(collection, query) {
    // query: mongodb query object (optional)
    var query = query || { };
    query['random'] = { $lte: Math.random() };
    var cur = collection.find(query).sort({ rand: -1 });
    if (! cur.hasNext()) {
        delete query.random;
        cur = collection.find(query).sort({ rand: -1 });
    }
    var doc = cur.next();
    doc.random = Math.random();
    collection.update({ _id: doc._id }, doc);
    return doc;
}

Также требуется добавить случайное "случайное" поле в ваши документы, поэтому не забудьте добавить это при их создании: вам может потребоваться инициализировать вашу коллекцию, как показано Джеффри

function addRandom(collection) { 
    collection.find().forEach(function (obj) {
        obj.random = Math.random();
        collection.save(obj);
    }); 
} 
db.eval(addRandom, db.things);

Результаты тестов

Этот метод намного быстрее, чем метод skip() (ceejayoz) и генерирует более равномерные случайные документы, чем метод "поваренной книги", сообщенный Майклом:

Для коллекции с 1 000 000 элементов:

  • Этот метод занимает меньше миллисекунды на моей машине.

  • Метод skip() в среднем занимает 180 мс

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

  • Этот метод будет выбирать все элементы равномерно с течением времени.

  • В моем тесте он был всего на 30% медленнее, чем метод поваренной книги.

  • случайность не совершенна на 100%, но она очень хороша (и при необходимости ее можно улучшить)

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

Ответ 6

Ниже приведен способ использования значений ObjectId по умолчанию для _id и небольшой математики и логики.

// Get the "min" and "max" timestamp values from the _id in the collection and the 
// diff between.
// 4-bytes from a hex string is 8 characters

var min = parseInt(db.collection.find()
        .sort({ "_id": 1 }).limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    max = parseInt(db.collection.find()
        .sort({ "_id": -1 })limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    diff = max - min;

// Get a random value from diff and divide/multiply be 1000 for The "_id" precision:
var random = Math.floor(Math.floor(Math.random(diff)*diff)/1000)*1000;

// Use "random" in the range and pad the hex string to a valid ObjectId
var _id = new ObjectId(((min + random)/1000).toString(16) + "0000000000000000")

// Then query for the single document:
var randomDoc = db.collection.find({ "_id": { "$gte": _id } })
   .sort({ "_id": 1 }).limit(1).toArray()[0];

Это общая логика в представлении оболочки и легко адаптируемая.

Итак, в точках:

  • Найдите минимальные и максимальные значения первичного ключа в коллекции

  • Создайте случайное число, которое находится между отметками времени этих документов.

  • Добавьте случайное число к минимальному значению и найдите первый документ, который больше или равен этому значению.

В этом случае используется значение "padding" из значения timestamp в "hex", чтобы сформировать действительное значение ObjectId, так как это то, что мы ищем. Использование целых чисел в качестве значения _id существенно проще, но та же основная идея в точках.

Ответ 7

В Python с использованием pymongo:

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]

Ответ 8

это сложно, если нет данных, которые можно отключить. что такое _id-поле? являются ли они идентификаторами объекта mongodb? Если это так, вы можете получить самые высокие и самые низкие значения:

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

то, если вы предполагаете, что идентификаторы равномерно распределены (но они не являются, но, по крайней мере, это начало):

unsigned long long L = first_8_bytes_of(lowest)
unsigned long long H = first_8_bytes_of(highest)

V = (H - L) * random_from_0_to_1();
N = L + V;
oid = N concat random_4_bytes();

randomobj = db.coll.find({_id:{$gte:oid}}).limit(1);

Ответ 9

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

var randRec = function() {
    // replace with your collection
    var coll = db.collection
    // get unixtime of first and last record
    var min = coll.find().sort({_id: 1}).limit(1)[0]._id.getTimestamp() - 0;
    var max = coll.find().sort({_id: -1}).limit(1)[0]._id.getTimestamp() - 0;

    // allow to pass additional query params
    return function(query) {
        if (typeof query === 'undefined') query = {}
        var randTime = Math.round(Math.random() * (max - min)) + min;
        var hexSeconds = Math.floor(randTime / 1000).toString(16);
        var id = ObjectId(hexSeconds + "0000000000000000");
        query._id = {$gte: id}
        return coll.find(query).limit(1)
    };
}();

Ответ 10

Теперь вы можете использовать агрегат. Пример:

db.users.aggregate(
   [ { $sample: { size: 3 } } ]
)

См. Документ.

Ответ 11

Мое решение по php:

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}

Ответ 12

Чтобы получить определенное количество случайных документов без дубликатов:

  • сначала получить все идентификаторы
  • получить размер документов
  • цикл, получающий случайный индекс и пропускающий дублированный

    number_of_docs=7
    db.collection('preguntas').find({},{_id:1}).toArray(function(err, arr) {
    count=arr.length
    idsram=[]
    rans=[]
    while(number_of_docs!=0){
        var R = Math.floor(Math.random() * count);
        if (rans.indexOf(R) > -1) {
         continue
          } else {           
                   ans.push(R)
                   idsram.push(arr[R]._id)
                   number_of_docs--
                    }
        }
    db.collection('preguntas').find({}).toArray(function(err1, doc1) {
                    if (err1) { console.log(err1); return;  }
                   res.send(doc1)
                });
            });
    

Ответ 13

Я бы предложил добавить случайное поле int каждому объекту. Тогда вы можете просто сделать

findOne({random_field: {$gte: rand()}}) 

выбрать случайный документ. Просто убедитесь, что вы гарантируетеIndex ({random_field: 1})

Ответ 14

Я бы предложил использовать map/reduce, где вы используете функцию карты, чтобы генерировать только тогда, когда случайное значение превышает заданную вероятность.

function mapf() {
    if(Math.random() <= probability) {
    emit(1, this);
    }
}

function reducef(key,values) {
    return {"documents": values};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": { "probability": 0.5}});
printjson(res.results);

Функция reducef выше работает, потому что из функции карты испускается только один ключ ('1').

Значение "вероятность" определяется в "области" при вызове mapRreduce (...)

Использование mapReduce, подобное этому, также должно быть использовано на sharded db.

Если вы хотите выбрать ровно n из m документов из db, вы можете сделать это следующим образом:

function mapf() {
    if(countSubset == 0) return;
    var prob = countSubset / countTotal;
    if(Math.random() <= prob) {
        emit(1, {"documents": [this]}); 
        countSubset--;
    }
    countTotal--;
}

function reducef(key,values) {
    var newArray = new Array();
for(var i=0; i < values.length; i++) {
    newArray = newArray.concat(values[i].documents);
}

return {"documents": newArray};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": {"countTotal": 4, "countSubset": 2}})
printjson(res.results);

Где "countTotal" (m) - количество документов в db, а "countSubset" (n) - количество документов для извлечения.

Этот подход может привести к некоторым проблемам с оштрафованными базами данных.

Ответ 15

Вы можете выбрать случайный _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)
            })
        })
    })

Здесь вам не нужно тратить пространство на хранение случайных чисел в коллекции.

Ответ 16

Используя Python (pymongo), функция агрегата также работает.

collection.aggregate([{'$sample': {'size': sample_size }}])

Этот подход намного быстрее, чем запуск запроса для случайного числа (например, collection.find([random_int]). Это особенно касается больших коллекций.

Ответ 17

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

Короче говоря, с требованием "разумно вращать" контент, что мы должны делать вместо случайного числа во всех документах, это включить персональный модификатор оценки q. Чтобы реализовать это самостоятельно, принимая небольшое количество пользователей, вы можете хранить документ для каждого пользователя, у которого есть productId, количество показаний, количество кликов, дата последнего просмотра и любые другие факторы, которые бизнес считает значимыми для вычисления оценки aq модификатор. При получении набора для отображения обычно вы запрашиваете больше документов из хранилища данных, чем запрашивается конечным пользователем, затем применяйте модификатор q-оценки, берете количество записей, запрашиваемых конечным пользователем, затем производите рандомизацию страницы результатов, крошечную set, поэтому просто отсортируйте документы на прикладном уровне (в памяти).

Если юниверс пользователей слишком велик, вы можете классифицировать пользователей по группам поведения и индексировать по группам поведения, а не пользователю.

Если юниверс продуктов достаточно мал, вы можете создать индекс для каждого пользователя.

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

Ответ 18

не решения для меня хорошо работали. особенно когда есть много пробелов, и набор мал. это работало очень хорошо для меня (в php):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();

Ответ 19

Если вы используете мангуст, вы можете использовать mongoose-random mongoose-random

Ответ 20

Если у вас есть простой ключ id, вы можете сохранить весь идентификатор в массиве, а затем выбрать случайный идентификатор. (Ответ Ruby):

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first

Ответ 21

Используя Map/Reduce, вы, безусловно, можете получить случайную запись, просто не обязательно очень эффективно, в зависимости от размера получаемой отфильтрованной коллекции, с которой вы работаете.

Я тестировал этот метод с 50 000 документов (фильтр уменьшает его до примерно 30 000), и он выполняется примерно в 400 мс на Intel i3 с 16 ГБ оперативной памятью и жестким диском SATA3...

db.toc_content.mapReduce(
    /* map function */
    function() { emit( 1, this._id ); },

    /* reduce function */
    function(k,v) {
        var r = Math.floor((Math.random()*v.length));
        return v[r];
    },

    /* options */
    {
        out: { inline: 1 },
        /* Filter the collection to "A"ctive documents */
        query: { status: "A" }
    }
);

Функция Map просто создает массив идентификатора всех документов, соответствующих запросу. В моем случае я проверил это примерно с 30 000 из 50 000 возможных документов.

Функция "Уменьшение" просто выбирает случайное целое число от 0 до количества элементов (-1) в массиве и затем возвращает этот массив _id.

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

Есть открытая проблема для MongoDB, чтобы включить эту функцию в ядро ​​... https://jira.mongodb.org/browse/SERVER-533

Если этот "случайный" выбор был встроен в индексный поиск вместо того, чтобы собирать идентификаторы в массив, а затем выбирать один, это поможет невероятно. (проголосуйте!)

Ответ 22

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

пс. Как найти случайные записи в mongodb вопрос отмечен как дубликат этого вопроса. Разница в том, что этот вопрос прямо спрашивает об одной записи, поскольку другой явно указывает на получение случайного документа s.

Ответ 23

Мой PHP/MongoDB сортировать/упорядочивать по СЛУЧАЙНОМУ решению. Надеюсь, это кому-нибудь поможет.

Примечание: у меня есть числовой идентификатор в моей коллекции MongoDB, который ссылается на запись базы данных MySQL.

Сначала я создаю массив из 10 случайно сгенерированных чисел

    $randomNumbers = [];
    for($i = 0; $i < 10; $i++){
        $randomNumbers[] = rand(0,1000);
    }

В своей агрегации я использую оператор конвейера $ addField в сочетании с $ arrayElemAt и $ mod (modulus). Оператор модуля даст мне число от 0 до 9, которое я затем использую, чтобы выбрать число из массива со случайными числами.

    $aggregate[] = [
        '$addFields' => [
            'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
        ],
    ];

После этого вы можете использовать сортировку Pipeline.

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];

Ответ 24

вы также можете использовать shuffle-array после выполнения вашего запроса

var shuffle = require ('shuffle-array');

Accounts.find(qry, function (err, results_array) {newIndexArr = shuffle (results_array);

Ответ 26

Если вы используете mongoid, оболочку document-to-object, вы можете сделать следующее в Рубин. (Предположим, что ваша модель - Пользователь)

User.all.to_a[rand(User.count)]

В моем .irbrc у меня есть

def rando klass
    klass.all.to_a[rand(klass.count)]
end

поэтому в консоли rails я могу сделать, например,

rando User
rando Article

для случайного получения документов из любой коллекции.

Ответ 27

Что работает эффективно и надежно:

Добавьте поле "случайный" в каждый документ и назначьте ему случайное значение, добавьте индекс для случайного поля и действуйте следующим образом:

Предположим, что у нас есть набор веб-ссылок, называемых "ссылками", и мы хотим получить от него случайную ссылку:

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

Чтобы убедиться, что одна и та же ссылка не появится во второй раз, обновите ее случайное поле новым случайным числом:

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