Случайная запись из MongoDB - PullRequest
288 голосов
/ 13 мая 2010

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

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

Есть предложения?

Ответы [ 27 ]

192 голосов
/ 07 ноября 2015

Начиная с версии 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, в возвращенном образце документа могут быть дубликаты.

115 голосов
/ 13 мая 2010

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

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
85 голосов
/ 01 апреля 2011

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

55 голосов
/ 29 февраля 2012

Вы также можете использовать функцию геопространственной индексации 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 геопункта, чтобы добавить второе измерение случайности в ваш запрос.

20 голосов
/ 19 февраля 2014

Следующий рецепт немного медленнее, чем решение монго поваренной книги (добавьте случайный ключ к каждому документу), но возвращает более равномерно распределенные случайные документы.Он немного менее равномерно распределен, чем решение 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% идеальна, но очень хороша(и при необходимости его можно улучшить)

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

9 голосов
/ 26 июня 2015

Вот способ использования значений по умолчанию 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" из значения метки времени в "hex" для формирования действительного значения ObjectId, поскольку именно это мы и ищем. Использование целых чисел в качестве значения _id существенно проще, но это та же основная идея в точках.

7 голосов
/ 24 января 2015

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

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]
6 голосов
/ 13 мая 2010

трудно, если там нет данных для отключения. что такое поле _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);
5 голосов
/ 05 декабря 2014

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

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)
    };
}();
5 голосов
/ 06 февраля 2017

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

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

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

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