Випадковий запис від MongoDB


336

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

Який найшвидший та найефективніший спосіб зробити це? Дані вже є, і немає поля, в якому я можу генерувати випадкове число і отримати випадковий рядок.

Будь-які пропозиції?


2
Дивіться також це питання SO під назвою "Порядок набору результатів, встановлених випадковим чином у монго" . Думаючи про випадкове впорядкування набору результатів, є більш загальною версією цього питання - більш потужною та кориснішою.
Девід Дж.

11
Це запитання постійно з’являється. Найновішу інформацію, можливо, можна знайти в запиті на функцію, щоб отримати випадкові предмети з колекції в трекер-касах MongoDB. Якщо він буде реалізований на національному рівні, це, мабуть, буде найбільш ефективним варіантом. (Якщо ви хочете, щоб ця функція перейшла, проголосуйте її.)
Девід Дж.

Це ошалена колекція?
Ділан Тонг

3
Правильну відповідь дав @JohnnyHK нижче: db.mycoll.aggregate ({$ sample: {size: 1}})
Флоріан

Хтось знає, наскільки це повільніше, ніж просто взяти перший запис? Я обговорюю, чи варто брати випадкову вибірку, щоб зробити щось проти просто робити це в порядку.
Девід Конг

Відповіді:


248

Починаючи з версії 3.2 MongoDB, ви можете отримати 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, може бути дублікатами.


12
Це хороший спосіб, але пам’ятайте, що НЕ гарантує, що в вибірці немає копій одного і того ж об’єкта.
Матей Арауджо

10
@MatheusAraujo, що не має значення, чи хочете ви один запис, але все одно хороший момент
Тобі

3
Не бути педантичним, але питання не вказує версію MongoDB, тому я вважаю, що найсвіжіша версія є розумною.
даланміллер

2
@Nepoxx Дивіться документи, які стосуються обробки.
JohnnyHK

2
@brycejl Це матиме фатальний недолік нічого не відповідати, якщо етап зразка $ не обрав жодних відповідних документів.
JohnnyHK

115

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

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

139
На жаль, пропуск () є досить неефективним, оскільки він повинен сканувати таку кількість документів. Крім того, існує умова перегонів, якщо рядки видаляються між отриманням підрахунку та запуском запиту.
mstearn

6
Зауважте, що випадкове число повинно бути від 0 до підрахунку (виключно). Тобто, якщо у вас є 10 елементів, випадкове число повинно бути від 0 до 9. Інакше курсор може спробувати пропустити повз останній елемент, і нічого не буде повернуто.
мат

4
Дякую, прекрасно працював у моїх цілях. @mstearn, ваші коментарі як до ефективності, так і до умов гонки є дійсними, але для колекцій, де жодне значення не має (одноразовий пакетний витяг із колекції, де записи не видаляються), це значно перевершує хакі (IMO) рішення в кулінарній книзі Монго.
Майкл Муса

4
що робить встановлення межі -1?
MonkeyBonkey

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Якщо числоToReturn дорівнює 0, db використовуватиме розмір повернення за замовчуванням. Якщо число негативне, то база даних поверне це число та закриє курсор. "
ceejayoz

86

Оновлення для MongoDB 3.2

3.2 введено зразок $ в трубопровід агрегації.

Існує також хороша публікація в блозі про її реалізацію.

Для старих версій (попередня відповідь)

Це насправді запит на функцію: 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до нього та додайте його ще раз.


7
І ось простий спосіб додати випадкове поле до кожного документа в колекції. функція setRandom () {db.topics.find (). forEach (функція (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Джеффрі

8
Це вибирає документ випадковим чином, але якщо ви робите це не один раз, пошук не є незалежним. Ви більше шансів отримати один і той же документ двічі поспіль, ніж випадкові випадки.
брак

12
Виглядає як погана реалізація кругового хешування. Це навіть гірше, ніж говорить недолік: навіть один пошук упереджений, оскільки випадкові числа розподілені не рівномірно. Щоб правильно це зробити, вам знадобиться набір, скажімо, 10 випадкових чисел на документ. Чим більше випадкових чисел ви використовуєте на один документ, тим рівномірнішим стає розподіл виводу.
Томас

4
Білет MongoDB JIRA все ще живий: jira.mongodb.org/browse/SERVER-533 Прокоментуйте та проголосуйте, якщо хочете цю функцію.
Девід Дж.

1
Зверніть увагу на згаданий тип застереження. Це не працює ефективно при невеликій кількості документів. Дано два пункти із випадковим ключем 3 та 63. Документ №63 вибиратиметься частіше там, де $gteперший. Альтернативне рішення stackoverflow.com/a/9499484/79201 працювало б краще в цьому випадку.
Райан Шумахер

56

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


8
Мені подобається ця відповідь, її найефективніша, яку я бачив, яка не потребує купою возитися з боку сервера.
Тоні Мільйон

4
Це також є упередженим щодо документів, у яких, мабуть, мало точок.
Томаса

6
Це правда, і тут є й інші проблеми: документи сильно співвідносяться на своїх випадкових ключах, тому дуже передбачувано, які документи будуть повернуті як група, якщо ви виберете кілька документів. Крім того, документи, близькі до меж (0 і 1), мають менше вибору. Останнє можна було вирішити за допомогою сферичного геомапування, яке обмотується по краях. Однак ви повинні бачити цю відповідь як вдосконалену версію рецепту кулінарної книги, а не як ідеальний механізм випадкового відбору. Це досить випадково для більшості цілей.
Ніко де Поель

@NicodePoel, мені подобається ваша відповідь, а також ваш коментар! І у вас є кілька запитань до вас: 1- Як ви знаєте, що точки, близькі до меж 0 і 1, менш вірогідні для вибору, чи це базується на якомусь математичному ґрунті? 2? Чи можете ви детальніше зупинитися на географічній кульовій сфері, як покращиться випадковий вибір і як це зробити в MongoDB? ... Вдячний!
securecurve

Оцініть свою ідею. Нарешті, у мене є чудовий код, який дуже зручний для процесора та оперативної пам’яті! Дякую
Qais Bsharat

21

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

Цей рецепт не є ідеальним - ідеальним рішенням буде вбудована функція, як відзначили інші.
Однак це має бути хорошим компромісом для багатьох цілей.


10

Ось спосіб, використовуючи 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значення істотно простіше, але однакова основна ідея в точках.


У мене колекція 300 000 000 рядків. Це єдине рішення, яке працює, і це досить швидко.
Нікос

8

У Python використовують pymongo:

import random

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

5
Варто зауважити, що всередині цього використовується використання пропуску та обмеження, як і багато інших відповідей.
JohnnyHK

Ваша відповідь правильна. Однак, будь ласка, замініть count()тим estimated_document_count(), що count()застаріло в Mongdo v4.2.
користувач3848207

8

Тепер ви можете використовувати агрегат. Приклад:

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

Дивіться док .


3
Примітка: зразок $ може отримати один і той же документ не один раз
Саман Шафіг,

6

важко, якщо немає даних там, щоб відключити. що таке поле _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);

1
Будь-які ідеї, як це виглядатиме в PHP? або хоча б якою мовою ви користувалися вище? це Python?
Марцін

6

За допомогою Python (pymongo) функція агрегату також працює.

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

Цей підхід набагато швидше, ніж виконання запиту для випадкового числа (наприклад, collection.find ([random_int]). Особливо це стосується великих колекцій).


5

Ви можете вибрати випадкову мітку часу та знайти перший об’єкт, який був створений згодом. Він буде сканувати лише один документ, хоча він не обов'язково дає вам рівномірний розподіл.

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)
    };
}();

Було б легко перекосити випадкову дату для обліку надлінійного зростання бази даних.
Мартін Новак

це найкращий метод для дуже великих колекцій, він працює в O (1), unline skip () або count (), що використовується в інших рішеннях тут
marmor

4

Моє рішення про 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;
}

3

Для отримання визначеної кількості випадкових документів без дублікатів:

  1. спочатку отримайте всі ідентифікатори
  2. отримати розмір документів
  3. циклічне отримання випадкового індексу та пропуск дублювання

    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)
                });
            });

2

Я б запропонував використовувати карту / зменшити, коли ви використовуєте функцію карти, щоб випромінювати лише тоді, коли випадкове значення вище заданої ймовірності.

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);

Вище функція reduf працює, тому що лише одна клавіша ("1") випускається з функції карти.

Значення "ймовірності" визначається у "області" при виклику mapRreduce (...)

Використовуючи mapReduce, подібний до цього, слід також використовуватись на шаруватий db.

Якщо ви хочете вибрати з nb рівно n м документів, ви можете зробити це так:

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) - кількість документів, які потрібно отримати.

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


4
Проведення сканування повної колекції для повернення 1 елемента ... це має бути найменш ефективним методом для цього.
Томаса

1
Хитрість полягає в тому, що це загальне рішення для повернення довільної кількості випадкових елементів - у цьому випадку це було б швидше, ніж інші рішення при отриманні> 2 випадкових елемента.
torbenl

2

Ви можете вибрати випадковий _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)
            })
        })
    })

Тут вам не потрібно витрачати місце на зберігання випадкових чисел у колекції.


1

Я б запропонував додати до кожного об'єкта випадкове поле int. Тоді ви можете просто зробити

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

вибрати випадковий документ. Просто переконайтеся, що ви забезпечили Індекс ({random_field: 1})


2
Якщо перший запис у вашій колекції має відносно високе значення random_field, чи не повертається він майже весь час?
thehiatus

2
thehaitus правильний, він буде - він не підходить для будь-яких цілей
Heptic

7
Це рішення є абсолютно неправильним, додавання випадкового числа (давайте уявимо їх між 0 a 2 ^ 32-1) не гарантує хорошого розподілу, а використання $ gte робить його ще гіршим, оскільки ваш випадковий вибір не буде навіть близьким до псевдовипадкового числа. Я пропоную ніколи не використовувати цю концепцію.
Максиміліано Ріос

1

Коли я зіткнувся з подібним рішенням, я відхилився і виявив, що бізнес-запит був насправді для створення певної форми повороту рекламних ресурсів, що подаються. У цьому випадку є набагато кращі варіанти, на які є відповіді в пошукових системах на зразок Solr, а не в сховищах даних, як MongoDB.

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

Якщо всесвіт користувачів занадто велика, ви можете класифікувати користувачів на групи поведінки та індексувати групи поведінки, а не користувача.

Якщо всесвіт продуктів недостатньо, ви можете створити індекс на користувача.

Я знайшов цю методику набагато ефективнішою, але що ще важливіше ефективнішою у створенні релевантного, гідного досвіду використання програмного рішення.


1

не з рішень добре працювали для мене. особливо коли прогалин багато, а набір невеликий. це дуже добре працювало для мене (у php):

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

Ви вказуєте мову, але не бібліотеку, яку ви використовуєте?
Бенджамін

FYI, тут є умова перегонів, якщо документ видалено між першим та третім рядком. Також find+ skipдосить погано, ви повертаєте всі документи лише для вибору одного: S.
Мартін Конечний


1

Мій PHP / MongoDB сортувати / замовити за рішенням RANDOM. Сподіваюсь, це допомагає комусь.

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

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

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

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

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

Після цього ви можете використовувати сорт Трубопровід.

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

0

Якщо у вас є простий ключ id, ви можете зберегти всі ідентифікатори в масиві, а потім вибрати випадковий ідентифікатор. (Рубі відповідь):

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

0

Використовуючи 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

Якщо цей "випадковий" вибір був вбудований у пошук індексу замість збирання ідентифікаторів у масив, а потім вибору одного, це допомогло б неймовірно. (голосуй!)


0

Це добре, це швидко, працює з декількома документами і не вимагає заповнення 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)
  }
})

пс. Як знайти випадкові записи у mongodb питання позначено як дублікат цього питання. Різниця полягає в тому, що це питання вимагає явно про один запис в інший явно про отримання випадкових документів сек .


-2

Якщо ви використовуєте монгоїд, обгортку документа-об'єкта, ви можете зробити наступне в Ruby. (Припустимо, що ваша модель є Користувачем)

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

У своєму .irbrc у мене є

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

тому в консольній рейці я можу робити, наприклад,

rando User
rando Article

щоб отримати документи випадковим чином із будь-якої колекції.


1
Це жахливо неефективно, оскільки він прочитає всю колекцію в масив, а потім вибере один запис.
JohnnyHK

Гаразд, може бути неефективним, але, безумовно, зручним. спробуйте це, якщо розмір ваших даних не надто великий
Zack Xu

3
Звичайно, але оригінальне запитання стосувалося колекції зі 100 мільйонами документів, тому це було б дуже поганим рішенням для цього випадку!
JohnnyHK

-2

Ви також можете використовувати shuffle-масив після виконання запиту

var shuffle = requ ('перетасувати масив');

Accounts.find (qry, функція (помилка, результат_ар'єр) {newIndexArr = перетасувати (результати_забір);


-7

Що працює ефективно та надійно:

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

Припустимо, у нас є колекція веб-посилань під назвою "посилання", і ми хочемо з неї випадкове посилання:

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

Щоб те саме посилання не з’явилося вдруге, оновіть його випадкове поле новим випадковим числом:

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

2
навіщо оновлювати базу даних, коли ви можете просто вибрати інший випадковий ключ?
Jason S

У вас може не бути списку ключів, з яких можна вибрати випадковим чином.
Майк

Отже, вам доведеться кожного разу сортувати всю колекцію? А як щодо нещасливих записів, які отримали великі випадкові числа? Вони ніколи не будуть відібрані.
Фантюс

1
Це потрібно зробити, тому що інші рішення, зокрема ті, що пропоновані в книзі MongoDB, не працюють. Якщо перше знаходження не вдається, друге знаходження завжди повертає предмет з найменшим випадковим значенням. Якщо індексувати випадковий низхідний спад, перший запит завжди повертає елемент із найбільшим випадковим числом.
потяг поїздів

Додавання поля в кожен документ? Я думаю, що це не доцільно.
CS_noob
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.