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

336

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

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

Какие-либо предложения?

Будет М
источник
2
См. Также этот вопрос, озаглавленный «Упорядочение набора результатов случайным образом в монго» . Размышление о случайном порядке набора результатов является более общей версией этого вопроса - более мощной и более полезной.
Дэвид Дж.
11
Этот вопрос постоянно всплывает. Новейшая информация, вероятно, может быть найдена по запросу функции для получения случайных элементов из коллекции в системе отслеживания билетов MongoDB. Если реализовано изначально, это, вероятно, будет наиболее эффективным вариантом. (Если вы хотите эту функцию, проголосуйте.)
Дэвид Дж.
Это осколочная коллекция?
Дилан Тонг
3
Правильный ответ был дан @JohnnyHK ниже: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian
Кто-нибудь знает, насколько это медленнее, чем просто взять первую запись? Я спорю, стоит ли брать случайную выборку, чтобы сделать что-то, а не делать это по порядку.
Дэвид Конг

Ответы:

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

JohnnyHK
источник
12
Это хороший способ, но помните, что он НЕ гарантирует, что в образце не будет копий одного и того же объекта.
Матеус Араужо
10
@MatheusAraujo, который не будет иметь значения, если вы хотите одну запись, но, тем не менее, хороший момент
Тоби,
3
Не для того, чтобы быть педантичным, но этот вопрос не определяет версию MongoDB, поэтому я предположил бы, что самая последняя версия является разумной.
dalanmiller
2
@Nepoxx См. Документы, касающиеся обработки.
JohnnyHK
2
@brycejl Это было бы роковым недостатком - ничего не совпадать, если на этапе $ sample не выбраны подходящие документы.
JohnnyHK
115

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

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
ceejayoz
источник
139
К сожалению, функция skip () довольно неэффективна, поскольку она сканирует столько документов. Кроме того, существует условие гонки, если между получением счетчика и выполнением запроса удаляются строки.
mstearn
6
Обратите внимание, что случайное число должно быть между 0 и количеством (исключая). То есть, если у вас есть 10 элементов, случайное число должно быть в диапазоне от 0 до 9. В противном случае курсор может попытаться пропустить последний элемент, и ничего не будет возвращено.
Мэтт
4
Спасибо, отлично сработало для моих целей. @mstearn, ваши комментарии как по эффективности, так и по условиям гонки действительны, но для коллекций, где это не имеет значения (одноразовый пакетный извлечение на стороне сервера в коллекции, где записи не удаляются), это значительно превосходит хакерство (IMO) решение в монго поваренной книги.
Майкл Муса
4
Что делает установка предела -1?
MonkeyBonkey
@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Если numberToReturn равно 0, база данных будет использовать размер по умолчанию. Если число отрицательное, тогда база данных вернет это число и закроет курсор. "
ceejayoz
86

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

Майкл
источник
7
И вот простой способ добавить случайное поле к каждому документу в коллекции. function setRandom () {db.topics.find (). forEach (function (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

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

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

spam_eggs
источник
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()as count()устарел в Mongdo v4.2.
user3848207
8

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

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

Смотрите док .

dbam
источник
3
Примечание: $ sample может получать один и тот же документ более одного раза
Саман Шафиг,
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? или хотя бы какой язык вы использовали выше? это питон?
Марчин
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;
}
code_turist
источник
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 также должно быть применимо к осколкам БД.

Если вы хотите выбрать ровно 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) - это количество документов, которые необходимо извлечь.

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

torbenl
источник
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)
            })
        })
    })

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

Vijay13
источник
1

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

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

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

mstearn
источник
2
Если первая запись в вашей коллекции имеет относительно высокое значение random_field, будет ли оно возвращаться почти все время?
thehiatus
2
Thehaitus правильно, это будет - это не подходит ни для каких целей
Heptic
7
Это решение совершенно неверно, добавление случайного числа (давайте представим, что между 0 a 2 ^ 32-1) не гарантирует какого-либо хорошего распределения, а использование $ gte делает его еще хуже, потому что ваш случайный выбор не будет даже близко на псевдослучайное число. Я предлагаю не использовать эту концепцию никогда.
Максимилиано Риос
1

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

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

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

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

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

paegun
источник
1

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

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

Мой 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
        ]
    ];
feskr
источник
0

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

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 возможных документов.

Функция Reduce просто выбирает случайное целое число между 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

Если вы используете mongoid, оболочку для документа к объекту, вы можете сделать следующее в 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
Хорошо, может быть, неэффективно, но, конечно, удобно. попробуйте это, если ваш размер данных не слишком велик
Зак Сю
3
Конечно, но первоначальный вопрос был о коллекции из 100 миллионов документов, так что это было бы очень плохим решением для этого случая!
JohnnyHK
-2

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

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

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

раби джем
источник
-7

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

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

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

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

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

db.links.update({random: Math.random()}, link)
крушение поезда
источник
2
Зачем обновлять базу данных, когда вы можете просто выбрать другой случайный ключ?
Джейсон С
У вас может не быть списка ключей для случайного выбора.
Майк
Таким образом, вы должны сортировать всю коллекцию каждый раз? А как насчет неудачных записей, которые получили большие случайные числа? Они никогда не будут выбраны.
Фанций
1
Вы должны сделать это, потому что другие решения, особенно предложенные в книге MongoDB, не работают. Если первая находка не удалась, вторая находка всегда возвращает элемент с наименьшим случайным значением. Если вы индексируете случайное значение по убыванию, первый запрос всегда возвращает элемент с наибольшим случайным числом.
крушение поезда
Добавление поля в каждый документ? Я думаю, что это не рекомендуется.
CS_noob