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

Я ищу случайную запись из огромной коллекции (100 миллионов записей).

Как это сделать быстрее и эффективнее?

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


person Will M    schedule 13.05.2010    source источник
comment
См. Также этот вопрос SO под заголовком «Порядок случайного набора результатов в mongo». Рассмотрение случайного упорядочивания набора результатов - это более общий вариант этого вопроса, более мощный и полезный.   -  person David J.    schedule 16.06.2012
comment
Этот вопрос все время всплывает. Последнюю информацию, вероятно, можно найти в запросе функции для получения случайных элементов из коллекции в трекере билетов MongoDB. Если реализовано изначально, это, вероятно, будет наиболее эффективным вариантом. (Если вам нужна эта функция, проголосуйте за нее.)   -  person David J.    schedule 17.06.2012
comment
Это сегментированная коллекция?   -  person Dylan Tong    schedule 27.07.2013
comment
Правильный ответ дал @JohnnyHK ниже: db.mycoll.aggregate ({$ sample: {size: 1}})   -  person Florian    schedule 24.03.2016
comment
Кто-нибудь знает, насколько это медленнее, чем просто снятие первой записи? Я обсуждаю, стоит ли брать случайную выборку, чтобы что-то сделать, или просто делать это по порядку.   -  person David Kong    schedule 06.02.2020
comment
На самом деле, противоположность ответов $ sample может быть не самым быстрым решением. Поскольку mongo может выполнять сканирование коллекции для случайной сортировки при использовании $ sample в зависимости от ситуации. См .: Ссылка: docs.mongodb.com/manual/reference/operator/ объединение / выборка   -  person cahit beyaz    schedule 05.12.2020


Ответы (28)


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

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

person JohnnyHK    schedule 07.11.2015
comment
Это хороший способ, но помните, что он НЕ гарантирует, что в образце нет копий одного и того же объекта. - person Matheus Araujo; 06.01.2016
comment
@MatheusAraujo, что не имеет значения, если вы хотите одну запись, но в любом случае хороший момент - person Toby; 10.01.2016
comment
@dalanmiller это только правильный ответ, если вы используете 3.2+, иначе это неправильный ответ. - person BanksySan; 07.04.2016
comment
Не хочу быть педантичным, но вопрос не указывает версию MongoDB, поэтому я полагаю, что наличие самой последней версии является разумным. - person dalanmiller; 07.04.2016
comment
Какова вычислительная сложность / стоимость этого? - person Nepoxx; 17.05.2016
comment
@Nepoxx См. документацию относительно задействованной обработки. - person JohnnyHK; 07.06.2016
comment
По этому вопросу существует какая-то проблема: stackoverflow.com/questions/37679999/ - person Steve Rossiter; 07.06.2016
comment
@Nepoxx запустил профилировщик, чтобы получить представление. 100 000 случайных документов, метод занял 0,155 секунды для выборки 10 документов. - person Dorival; 08.06.2016
comment
@MatheusAraujo, потом довольно легко отфильтровать дубликат - person Hai Phaikawl; 17.09.2017
comment
.aggregate ([{$ sample: {size: 1}}]) - этот синтаксис работал у меня в mongo 3.4 - person Tebe; 04.10.2018
comment
@Tebe Хороший момент, теперь требуется конвейер массивов. Обновлено. - person JohnnyHK; 04.10.2018
comment
теперь ответ не сработает для парней с mongo 3.2 и, возможно, ниже) - person Tebe; 04.10.2018
comment
@Tebe Нет, синтаксис массива всегда поддерживался, просто раньше он был необязательным. - person JohnnyHK; 04.10.2018
comment
Полезным дополнением является упоминание о том, что для ограничения поиска в соответствии с определенными парами "ключ-значение" вы можете: db.mycoll.aggregate([{ $sample: { size: 1 } }, { $match: {key1: value1, key2: value2, ...}}]) - person ThisIsNotAnId; 22.02.2019
comment
@ThisIsNotAnId Верно, но обычно сначала нужно поставить этап $match. - person JohnnyHK; 22.02.2019
comment
@JohnnyHK Верно, я понял это позже. Полная ошибка новичка здесь. - person ThisIsNotAnId; 23.02.2019
comment
@HaiPhaikawl, как мы можем удалить дубликаты из результатов этапов $ sample и $ match ($ match - это первый этап)? - person shivgre; 16.04.2019
comment
Перед сопоставлением необходимо выполнить выборку для оптимальной производительности. См. Статью здесь danielhnyk.cz/randomly-choose-n-records-from- mongodb - person brycejl; 19.04.2020
comment
@brycejl Это имело бы фатальный недостаток, заключающийся в том, что ничего не соответствовало бы, если бы на этапе $ sample не были выбраны никакие совпадающие документы. - person JohnnyHK; 19.04.2020
comment
Это слишком медленно! с образцом 0,2с. без 0,012 с - person John Tribe; 10.07.2020
comment
Я написал пакет npmjs.com/package/unique-random-docs которые исправляют проблемы с дублированием и возвращают только уникальные документы npm i unique-random-docs не стесняйтесь проверить это! - person Detoner; 01.03.2021

Подсчитайте все записи, сгенерируйте случайное число от 0 до count, а затем выполните:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
person ceejayoz    schedule 13.05.2010
comment
К сожалению, skip () довольно неэффективен, поскольку ему приходится сканировать такое количество документов. Кроме того, существует состояние гонки, если строки удаляются между получением счетчика и выполнением запроса. - person mstearn; 17.05.2010
comment
Обратите внимание, что случайное число должно быть от 0 до счетчика (исключая). То есть, если у вас 10 элементов, случайное число должно быть от 0 до 9. В противном случае курсор может попытаться пропустить последний элемент, и ничего не будет возвращено. - person matt; 21.04.2011
comment
Спасибо, отлично сработал для моих целей. @mstearn, ваши комментарии как об эффективности, так и об условиях гонки действительны, но для коллекций, где ни одно не имеет значения (одноразовое извлечение партии на стороне сервера в коллекции, где записи не удаляются), это значительно превосходит хакерский (IMO) решение в Mongo Cookbook. - person Michael Moussa; 05.09.2012
comment
что делает установка лимита на -1? - person MonkeyBonkey; 27.01.2013
comment
@MonkeyBonkey docs.mongodb.org/meta-driver/latest / legacy / Если numberToReturn равно 0, база данных будет использовать размер возвращаемого значения по умолчанию. Если число отрицательное, база данных вернет это число и закроет курсор. - person ceejayoz; 27.01.2013
comment
Это действительно кажется наиболее жизнеспособным решением для равномерно распределенного случайного выбора. Другой подход заключался бы в создании последовательного идентификатора для каждого из документов - возможно, в качестве другой коллекции, чтобы не обновлять исходный документ, а затем использовать случайное число в количестве имеющихся документов для выбора одного из них. Конечно, это может быстро устареть, но его можно использовать для случайного выбора нескольких документов, создав список один раз и предоставив матрицу чисел, для которых нужно получить идентификаторы объектов. - person David Burton; 11.07.2013
comment
Если вам нужна более высокая эффективность, чем эта, то модификация рецепта поваренной книги может дать равномерно распределенные случайные записи. Смотрите мой ответ ниже. - person spam_eggs; 09.02.2014
comment
Если вам интересно, как подсчитать все записи, см. команду count. - person Boaz - CorporateShillExchange; 11.02.2015
comment
Если ваша коллекция равномерно распределена во времени, вы можете использовать временную метку для эффективного выбора случайной записи. См. stackoverflow.com/questions/2824157/random -Record-from-mongodb /. - person Martin Nowak; 31.03.2015

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

person Michael    schedule 01.04.2011
comment
А вот простой способ добавить случайное поле к каждому документу в коллекции. функция setRandom () {db.topics.find (). forEach (функция (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom); - person Geoffrey; 01.06.2011
comment
Запрос функции был повторно открыт, но еще не запланирован. - person Leopd; 28.10.2011
comment
При этом документ выбирается случайным образом, но если вы сделаете это более одного раза, поиск не будет независимым. У вас больше шансов получить один и тот же документ дважды подряд, чем это диктует случайность. - person lacker; 10.01.2012
comment
Похоже на плохую реализацию кругового хеширования. Это даже хуже, чем говорит лакер: даже один поиск смещен, потому что случайные числа распределяются неравномерно. Чтобы сделать это правильно, вам понадобится набор, скажем, из 10 случайных чисел на документ. Чем больше случайных чисел вы используете для каждого документа, тем более равномерным становится выходное распределение. - person Thomas; 30.03.2012
comment
Билет MongoDB JIRA все еще жив: jira.mongodb.org/browse/SERVER-533 Прокомментируйте и проголосуйте, если хотите эту функцию. - person David J.; 16.06.2012
comment
Обратите внимание на указанное предостережение. Это не работает эффективно с небольшим количеством документов. Даны два элемента со случайным ключом 3 и 63. Чаще будет выбираться документ №63, где первым стоит $gte. В этом случае лучше подойдет альтернативное решение stackoverflow.com/a/9499484/79201. - person Ryan Schumacher; 30.10.2013
comment
Предвзятость может быть устранена путем генерации новых случайных чисел по мере продвижения. Я опубликую ответ, описывающий это более подробно. - person spam_eggs; 07.02.2014
comment
Если, например, первый документ вашей коллекции имеет random = 0.8, тогда random: {$ gte: rand} вернет этот первый документ для всех случайных значений ‹= 0.8. На самом деле это ужасное решение, мне интересно, почему оно так популярно в Интернете. - person Anton Petrov; 27.03.2014
comment
если вы не можете гарантировать, что у вас есть МНОГО документов и равномерное распределение, это очень плохое решение, поскольку оно имеет тенденцию часто выдавать один и тот же документ. - person pomarc; 19.04.2014
comment
так как map reduce сортирует ввод по ключу, можно использовать это поведение, чтобы получить наиболее близкий результат, просто выбрав .first из результатов (или .last в случае lte (param) - person mmln; 05.05.2014
comment
Чтобы максимально равномерно распределить результаты, вы можете использовать findAndModify() и обновлять случайное поле вместе с каждым запросом. - person Julien; 05.10.2014
comment
Если вы хотите выполнить два запроса и выполнить обновление, вы можете исправить проблему случайности, выбрав обе записи: $ gte и $ lte. Затем верните запись, наиболее близкую к случайному значению. Затем обновите запись, чтобы иметь новую случайную запись. - person diedthreetimes; 24.11.2014
comment
Похоже, запрос функции был подтвержден и исправлен совсем недавно (2015/10, в версии 3.1.6). Вы можете обновить свой ответ. :) - person grapeot; 11.11.2015
comment
Но когда я это сделаю, мои статьи исчезнут - person Alp Eren Gül; 01.06.2020

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

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

person Nico de Poel    schedule 29.02.2012
comment
Мне нравится этот ответ, это самый эффективный из тех, что я когда-либо видел, и который не требует кучи возни с сервером. - person Tony Million; 10.03.2012
comment
Это также предвзятое отношение к документам, вблизи которых мало точек. - person Thomas; 30.03.2012
comment
Это правда, но есть и другие проблемы: документы сильно коррелированы по своим случайным ключам, поэтому очень предсказуемо, какие документы будут возвращены как группа, если вы выберете несколько документов. Кроме того, менее вероятно, что будут выбраны документы, близкие к границам (0 и 1). Последняя может быть решена с помощью сферической картографии, которая оборачивается по краям. Однако вы должны рассматривать этот ответ как улучшенную версию рецепта из поваренной книги, а не как идеальный механизм случайного выбора. Это достаточно случайно для большинства целей. - person Nico de Poel; 30.03.2012
comment
@NicodePoel, мне нравится ваш ответ и ваш комментарий! И у меня есть пара вопросов к вам: 1- Откуда вы знаете, что точки, близкие к границам 0 и 1, с меньшей вероятностью будут выбраны, это основано на какой-то математической основе? 2- Можете ли вы подробнее рассказать о сферической геометрии, как это улучшит случайный выбор и как это сделать в MongoDB? ... Оценил! - person securecurve; 10.09.2015
comment
Оцените вашу идею. Наконец, у меня есть отличный код, который очень удобен для ЦП и ОЗУ! Спасибо - person Qais Bsharat; 04.03.2020

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

Для коллекции из 1000000 элементов:

  • На моем компьютере этот метод занимает меньше миллисекунды.

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

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

  • Этот метод со временем подберет все элементы равномерно.

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

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

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

person spam_eggs    schedule 18.02.2014

Вот способ использования значений по умолчанию 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];

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

Итак, по пунктам:

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

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

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

При этом используется «отступ» от значения временной метки в «шестнадцатеричном формате» для формирования допустимого значения ObjectId, поскольку это то, что мы ищем. Использование целых чисел в качестве значения _id существенно проще, но та же основная идея в точках.

person Blakes Seven    schedule 26.06.2015
comment
У меня коллекция 300 000 000 строк. Это единственное решение, которое работает, и оно достаточно быстрое. - person Nikos; 14.04.2019

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

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

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

person dbam    schedule 06.02.2017
comment
Примечание: $ sample может получать один и тот же документ более одного раза - person Saman; 29.05.2017

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

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]
person Jabba    schedule 24.01.2015
comment
Стоит отметить, что внутри он будет использовать пропуск и ограничение, как и многие другие ответы. - person JohnnyHK; 24.01.2015
comment
Ваш ответ правильный. Однако замените count() на estimated_document_count(), поскольку count() устарел в Mongdo v4.2. - person user3848207; 12.06.2020

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

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

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

person Daniel    schedule 17.04.2018

это сложно, если нет данных, которые можно было бы отключить. что такое поле _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);
person dm.    schedule 13.05.2010
comment
Есть идеи, как это будет выглядеть в PHP? или хотя бы какой язык вы использовали выше? это Python? - person Marcin; 20.05.2013

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

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)
    };
}();
person Martin Nowak    schedule 04.12.2014
comment
Было бы легко исказить случайную дату, чтобы учесть сверхлинейный рост базы данных. - person Martin Nowak; 31.03.2015
comment
это лучший метод для очень больших коллекций, он работает на O (1), unline skip () или count (), используемых в других решениях здесь - person marmor; 02.11.2016

Мое решение на 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;
}
person code_turist    schedule 23.12.2014

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

  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)
                });
            });
    
person Fabio Guerra    schedule 19.12.2015

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

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 также можно использовать с сегментированной базой данных.

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

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) - количество документов в базе данных, а countSubset (n) - количество документов для извлечения.

Такой подход может вызвать некоторые проблемы с сегментированными базами данных.

person torbenl    schedule 26.02.2012
comment
Выполнение полного сканирования коллекции для возврата 1 элемента ... это, должно быть, наименее эффективный метод для этого. - person Thomas; 30.03.2012
comment
Хитрость в том, что это общее решение для возврата произвольного числа случайных элементов - и в этом случае оно будет быстрее других решений при получении ›2 случайных элементов. - person torbenl; 06.02.2014

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

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

person Vijay13    schedule 30.04.2015

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

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

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

person mstearn    schedule 17.05.2010
comment
Если первая запись в вашей коллекции имеет относительно высокое значение random_field, не будет ли она возвращаться почти все время? - person thehiatus; 24.01.2013
comment
thehaitus правильный, будет - он не подходит ни для каких целей - person Heptic; 08.08.2013
comment
Это решение совершенно неверно, добавление случайного числа (представим, между 0 и 2 ^ 32-1) не гарантирует хорошего распределения, а использование $ gte делает его еще хуже, потому что ваш случайный выбор даже близко не будет в псевдослучайное число. Я предлагаю никогда не использовать эту концепцию. - person Maximiliano Rios; 03.12.2013

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

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

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

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

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

person paegun    schedule 11.09.2013

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

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();
person Mantas Karanauskas    schedule 21.01.2014
comment
Вы указываете язык, а не библиотеку, которую используете? - person BenMorel; 21.01.2014
comment
К вашему сведению, здесь возникает состояние гонки, если документ удаляется между первой и третьей строкой. Также find + skip - это очень плохо, вы возвращаете все документы, чтобы выбрать один: S. - person Martin Konecny; 28.07.2014

Моя сортировка / сортировка 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 ] ] ] ],
        ],
    ];

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

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];
person feskr    schedule 20.12.2018

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

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

https://docs.mongodb.com/manual/reference/operator/aggregation/sample/

person Anup Panwar    schedule 16.10.2020

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

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first
person Mr. Demetrius Michael    schedule 19.03.2013

Используя 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 возможных документов.

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

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

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

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

person doublehelix    schedule 29.01.2014

Это работает хорошо, быстро, работает с несколькими документами и не требует заполнения поля 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 вопрос отмечен как дубликат этого вопрос. Разница в том, что в этом вопросе явно задается вопрос об отдельной записи, а в другом - о получении случайных документов .

person Mirek Rusin    schedule 19.11.2014

MongoDB теперь имеет $ rand

Чтобы выбрать n неповторяющихся элементов, объедините их с { $addFields: { _f: { $rand: {} } } }, затем $sort по _f и $limit n.

person Polv    schedule 23.02.2021

Лучший способ в Mongoose - выполнить вызов агрегирования с помощью $ sample. Однако Mongoose не применяет документы Mongoose к Aggregation, особенно если также необходимо применить populate ().

Для получения бережливого массива из базы данных:

/*
Sample model should be init first
const Sample = mongoose …
*/

const samples = await Sample.aggregate([
  { $match: {} },
  { $sample: { size: 33 } },
]).exec();
console.log(samples); //a lean Array

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

const samples = (
  await Sample.aggregate([
    { $match: {} },
    { $sample: { size: 27 } },
    { $project: { _id: 1 } },
  ]).exec()
).map(v => v._id);

const mongooseSamples = await Sample.find({ _id: { $in: samples } });

console.log(mongooseSamples); //an Array of mongoose documents
person TG___    schedule 06.04.2021

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

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

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

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

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

rando User
rando Article

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

person Zack Xu    schedule 06.12.2013
comment
Это ужасно неэффективно, поскольку он считывает всю коллекцию в массив, а затем выбирает одну запись. - person JohnnyHK; 06.12.2013
comment
Хорошо, может быть, неэффективно, но определенно удобно. попробуйте это, если ваш размер данных не слишком велик - person Zack Xu; 06.12.2013
comment
Конечно, но исходный вопрос был о коллекции из 100 миллионов документов, так что это было бы очень плохим решением для этого случая! - person JohnnyHK; 06.12.2013

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

var shuffle = require ('случайный массив');

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

person Community    schedule 12.05.2019

Эффективно и надежно работает следующее:

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

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

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

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

db.links.update({random: Math.random()}, link)
person trainwreck    schedule 25.03.2011
comment
зачем обновлять базу данных, если можно просто выбрать другой случайный ключ? - person Jason S; 08.04.2011
comment
У вас может не быть списка ключей для случайного выбора. - person Mike; 21.08.2011
comment
Значит, вам каждый раз приходится сортировать всю коллекцию? А как быть с неудачными записями с большими случайными числами? Они никогда не будут выбраны. - person Fantius; 11.01.2012
comment
Вы должны сделать это, потому что другие решения, особенно предлагаемое в книге MongoDB, не работают. Если первая попытка не удалась, вторая всегда возвращает элемент с наименьшим случайным значением. Если вы индексируете random по убыванию, первый запрос всегда возвращает элемент с наибольшим случайным числом. - person trainwreck; 17.01.2012
comment
Добавляете поле в каждый документ? Я считаю это нецелесообразным. - person CS_noob; 16.07.2016