MongoDB:как найти 10 случайных документов в коллекции из 100?

способен ли MongoDB финансировать количество случайных документов без создания нескольких запросов?

например, я реализовал на стороне JS после загрузки всего документа в коллекции, что является расточительным-следовательно, просто хотел проверить, можно ли это сделать лучше с одним запросом db?

путь, который я взял на стороне JS:

  • получить все сведения
  • сделать массив идентификаторов
  • shuffle массив идентификаторов (случайный порядок)
  • соедините массив с требуемым количеством документов
  • создайте список документов, выбрав их по ID, который мы оставили после двух предыдущих операций, одну за другой из всей коллекции

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

любое предложение очень ценится

4 ответов


как опубликовано в другом ответе, MongoDB теперь поддерживает выборка в рамках агрегирования начиная с версии 3.2:

способ, которым вы могли бы это сделать:

db.products.aggregate([{$sample: {size: 5}}]); // You want to get 5 docs

или:

db.products.aggregate([
  {$match: {category:"Electronic Devices"}}, // filter the results
  {$sample: {size: 5}} // You want to get 5 docs
]);
, есть предупреждения об операторе $ sample:

(по состоянию на 6 ноября 2017 года, где последняя версия 3.4) => If ничего из этого не выполнено:

  • $sample является первой стадией трубопровода
  • N составляет менее 5% от общего количества документов в коллекции
  • коллекция содержит более 100 документов

если какое-либо из вышеперечисленных условий не выполняется, $sample выполняет сканирование коллекции с последующей случайной сортировкой для выбора N документов.

как в последнем примере с $ match

ОТВЕТ

вы всегда можете запустить:

db.products.find({category:"Electronic Devices"}).skip(Math.random()*YOUR_COLLECTION_SIZE)

но заказ не будет случайным, и вам понадобятся два запроса (один счетчик, чтобы получить YOUR_COLLECTION_SIZE) или оценить, насколько он велик (это около 100 записей, около 1000, около 10000...)

вы также можете добавить поле ко всем документам со случайным числом и запросить по этому номеру. Недостатком здесь будет то, что вы получите те же результаты каждый раз, когда вы запустите тот же вопрос. Чтобы исправить это, вы всегда можете играть с limit и пропустить или даже с сортировкой. вы также можете обновлять эти случайные числа каждый раз, когда вы получаете запись (подразумевает больше запросов).

--Я не знаю, используете ли вы Mongoose, Mondoid или непосредственно драйвер Mongo для любого конкретного языка, поэтому я напишу все о Mongo shell.

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

{
 _id: ObjectId("..."),
 name: "Awesome Product",
 category: "Electronic Devices",
}

и я бы предложил использовать:

{
 _id: ObjectId("..."),
 name: "Awesome Product",
 category: "Electronic Devices",
 _random_sample: Math.random()
}

затем вы могли бы сделать:

db.products.find({category:"Electronic Devices",_random_sample:{$gte:Math.random()}})

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

var your_query = {} //it would impact in your performance if there are a lot of records
your_query = {category: "Electronic Devices"} //Update 
//upsert = false, multi = true
db.products.update(your_query,{$set:{_random_sample::Math.random()}},false,true)

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

for(var i = 0; i < records.length; i++){
   var query = {_id: records[i]._id};
   //upsert = false, multi = false
   db.products.update(query,{$set:{_random_sample::Math.random()}},false,false);
}

редактировать

имейте в виду, что

db.products.update(your_query,{$set:{_random_sample::Math.random()}},false,true)

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


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

$образец Новое в версии 3.2.

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

этап $ sample имеет следующий синтаксис:

{ $sample: { size: <positive integer> } }

Источник: MongoDB Docs

в этом случае:

db.products.aggregate([{$sample: {size: 10}}]);

вот что я придумал в конце:

var numberOfItems = 10;


// GET LIST OF ALL ID's
SchemaNameHere.find({}, { '_id': 1 }, function(err, data) {

    if (err) res.send(err);

    // shuffle array, as per here  https://github.com/coolaj86/knuth-shuffle
    var arr = shuffle(data.slice(0));

    // get only the first numberOfItems of the shuffled array
    arr.splice(numberOfItems, arr.length - numberOfItems);

    // new array to store all items
    var return_arr = [];

    // use async each, as per here http://justinklemm.com/node-js-async-tutorial/
    async.each(arr, function(item, callback) {

        // get items 1 by 1 and add to the return_arr
        SchemaNameHere.findById(item._id, function(err, data) {

            if (err) res.send(err);
            return_arr.push(data);

            // go to the next one item, or to the next function if done
            callback();

        });

    }, function(err) {

        // run this when looped through all items in arr
        res.json(return_arr);

    });

});

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

var randomDoc = db.getCollection("collectionName").aggregate([ {
    $match : {
// criteria to filter matches
    }
}, {
    $sample : {
        size : 1
    }
} ]).result[0];

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