MongoDB'den rastgele kayıt


336

Çok büyük bir kayıttan (100 milyon kayıt) rastgele bir kayıt almak istiyorum mongodb.

Bunu yapmanın en hızlı ve etkili yolu nedir? Veriler zaten orada ve rastgele bir sayı üretip rastgele bir satır elde edebileceğim bir alan yok.

Herhangi bir öneri?


2
Ayrıca bkz . "Moğolda rastgele bir sonuç kümesi sipariş etme" başlıklı SO sorusu . Bir sonuç kümesini rastgele sıralamayı düşünmek bu sorunun daha genel bir sürümüdür - daha güçlü ve daha yararlı.
David J.

11
Bu soru ortaya çıkıyor. En son bilgiler , MongoDB bilet izleyicisindeki bir koleksiyondan rastgele öğeler almak için özellik isteğinde bulunabilir . Yerel olarak uygulanırsa, muhtemelen en verimli seçenek olacaktır. (Özelliği istiyorsanız, oy verin.)
David J.

Bu parçalanmış bir koleksiyon mu?
Dylan Tong

3
Doğru cevap aşağıdaki @JohnnyHK
Florian

Bunun sadece ilk kaydı almaktan ne kadar yavaş olduğunu bilen var mı? Bir şeyi yapmak için rastgele bir örnek almaya değip değmeyeceğimi tartışıyorum.
David Kong

Yanıtlar:


248

MongoDB'nin 3.2 sürümünden başlayarak, $sampletoplama boru hattı operatörünü kullanarak bir koleksiyondan N rastgele doküman alabilirsiniz :

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

Koleksiyonun filtrelenmiş bir alt kümesinden rasgele belgeleri seçmek istiyorsanız $match, boru hattına bir sahne ekleyin :

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

Yorumlarda belirtildiği gibi size, 1'den büyük olduğunda, iade edilen belge örneğinde kopyalar olabilir.


12
Bu iyi bir yoldur, ancak numunede aynı nesnenin hiçbir kopyası olmadığını garanti etmediğini unutmayın.
Matheus Araujo

10
@MatheusAraujo bir rekor ama iyi bir nokta istersen önemli olmayacak
Toby

3
Bilgiçlik taslamayacak ama soru bir MongoDB sürümü belirtmiyor, bu yüzden en son sürüme sahip olmanın makul olduğunu varsayıyorum.
dalanmiller

2
@Nepoxx İlgili işleme ilişkin dokümanlara bakın .
JohnnyHK

2
@brycejl $ sample aşaması eşleşen herhangi bir belge seçmeseydi, hiçbir şeyle eşleşmemenin ölümcül kusuru olurdu.
JohnnyHK

115

Tüm kayıtların sayısını yapın, 0 ile sayı arasında rastgele bir sayı oluşturun ve sonra şunları yapın:

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

139
Maalesef skip (), bu kadar çok belgeyi taraması gerektiğinden oldukça verimsizdir. Ayrıca, sayıyı alma ve sorguyu çalıştırma arasında satırlar kaldırılırsa bir yarış koşulu vardır.
49'da mstearn

6
Rastgele sayının 0 ile sayı (özel) arasında olması gerektiğini unutmayın. Yani, 10 öğeniz varsa, rastgele sayı 0 ile 9 arasında olmalıdır. Aksi takdirde imleç son öğeyi atlamaya çalışabilir ve hiçbir şey döndürülmez.
matt

4
Teşekkürler, benim amacım için mükemmel çalıştı. @mstearn, hem verimlilik hem de yarış koşulları hakkındaki yorumlarınız geçerlidir, ancak hiçbirinin önemli olmadığı koleksiyonlar için (kayıtların silinmediği bir koleksiyonda bir kerelik sunucu tarafı toplu ekstrakt), bu hacky'den (IMO) büyük ölçüde üstündür Yemek Tarifleri
Michael Moussa

4
sınırı -1 olarak ayarlamak ne yapar?
MonkeyBonkey

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "numberToReturn 0 ise, db varsayılan dönüş boyutunu kullanır. Sayı negatifse, veritabanı bu sayıyı döndürür ve imleci kapatır. "
ceejayoz

86

MongoDB 3.2 Güncellemesi

3.2 , toplama boru hattına $ örnek tanıttı .

Ayrıca uygulamaya koymak için iyi bir blog yazısı var.

Daha eski sürümler için (önceki yanıt)

Bu aslında bir özellik isteğiydi : http://jira.mongodb.org/browse/SERVER-533 ancak " Düzeltilmeyecek " başlığı altında dosyalandı.

Yemek kitabının koleksiyondan rastgele bir belge seçmek için çok iyi bir tarifi var: http://cookbook.mongodb.org/patterns/random-attribute/

Tarifi yeniden yorumlamak için belgelerinize rastgele sayılar atarsınız:

db.docs.save( { key : 1, ..., random : Math.random() } )

Ardından rastgele bir belge seçin:

rand = Math.random()
result = db.docs.findOne( { key : 2, random : { $gte : rand } } )
if ( result == null ) {
  result = db.docs.findOne( { key : 2, random : { $lte : rand } } )
}

Her ikisiyle de sorgulama yapmak $gteve $lterastgele bir sayıya sahip belgeyi bulmak için gereklidir rand.

Ve elbette rastgele alanda dizin oluşturmak isteyeceksiniz:

db.docs.ensureIndex( { key : 1, random :1 } )

Zaten bir dizine karşı sorgu yapıyorsanız, dizine bırakın, ekleyin random: 1ve tekrar ekleyin.


7
Ve koleksiyondaki her belgeye rastgele alan eklemenin basit bir yolu. işlev setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey

8
Bu, bir belgeyi rastgele seçer, ancak bir kereden fazla yaparsanız, aramalar bağımsız değildir. Aynı belgeyi arka arkaya iki kez elde etme olasılığınızın, rastgele bir şansın dikte edeceğinden daha olasıdır.
lacker

12
Dairesel karma işleminin kötü bir uygulaması gibi görünüyor. Lacker'ın söylediklerinden bile daha kötü: bir arama bile önyargılı çünkü rastgele sayılar eşit olarak dağıtılmıyor. Bunu doğru bir şekilde yapmak için, her belge için 10 rasgele sayı kümesine ihtiyacınız olacaktır. Belge başına ne kadar rasgele sayılar kullanırsanız, çıktı dağılımı o kadar düzgün olur.
Thomas

4
MongoDB JIRA bileti hala hayatta: jira.mongodb.org/browse/SERVER-533 Özelliği istiyorsanız yorum yapın ve oy verin.
David J.

1
Belirtilen uyarı türüne dikkat edin. Bu, az miktarda belge ile verimli bir şekilde çalışmaz. 3 ve 63 numaralı rastgele anahtarlı iki öğe verilir. 63 numaralı belge $gte, ilk sırada daha sık seçilecektir . Alternatif çözüm stackoverflow.com/a/9499484/79201 bu durumda daha iyi çalışır.
Ryan Schumacher

56

Rastgele bir sayıya 'en yakın' belgeleri seçmek için MongoDB'nin coğrafi dizine ekleme özelliğini de kullanabilirsiniz.

İlk olarak, bir koleksiyonda coğrafi uzamsal dizine ekleme özelliğini etkinleştirin:

db.docs.ensureIndex( { random_point: '2d' } )

X ekseninde rastgele noktaları olan bir grup belge oluşturmak için:

for ( i = 0; i < 10; ++i ) {
    db.docs.insert( { key: i, random_point: [Math.random(), 0] } );
}

Sonra koleksiyondan rastgele bir belge alabilirsiniz:

db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )

Veya rastgele bir noktaya en yakın birkaç belgeyi alabilirsiniz:

db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

Bu sadece bir sorgu gerektirir ve null kontrol gerektirmez, ayrıca kod temiz, basit ve esnektir. Sorgunuza ikinci bir rasgelelik boyutu eklemek için coğrafi noktanın Y eksenini bile kullanabilirsiniz.


8
Bu yanıtı beğendim, sunucu tarafı hakkında karışıklık gerektirmeyen gördüğüm en verimli olanı.
Tony Million

4
Bu aynı zamanda, çevrelerinde çok az puan olan belgelere de yöneliktir.
Thomas

6
Bu doğrudur ve başka problemler de vardır: belgeler rastgele anahtarlarıyla güçlü bir şekilde ilişkilidir, bu nedenle birden fazla belge seçerseniz hangi belgelerin grup olarak döndürüleceği oldukça tahmin edilebilir. Ayrıca, sınırlara (0 ve 1) yakın belgelerin seçilmesi daha az olasıdır. İkincisi, kenarları saran küresel coğrafi haritalama kullanılarak çözülebilir. Bununla birlikte, bu cevabı mükemmel bir rastgele seçim mekanizması olarak değil, yemek tarifinin geliştirilmiş bir versiyonu olarak görmelisiniz. Çoğu amaç için yeterince rastgele.
Nico de Poel

@NicodePoel, cevabınızı ve yorumunuzu beğendim! Ve size birkaç sorum var: 1- 0 ve 1 sınırlarına yakın noktaların seçilme olasılığının daha düşük olduğunu nasıl biliyorsunuz, bu matematiksel bir temele dayanıyor mu ?, 2- Küresel coğrafi haritalama hakkında daha fazla ayrıntı verebilir misiniz rastgele seçimi nasıl daha iyi olacak ve MongoDB'de nasıl yapılır? ... Takdir!
sececurve

Fikrinizi onaylayın. Son olarak, çok CPU ve RAM dostu harika bir kod var! Teşekkür ederim
Qais Bsharat

21

Aşağıdaki tarif, mongo yemek kitabı çözümünden biraz daha yavaştır (her belgeye rastgele bir anahtar ekleyin), ancak daha eşit dağıtılmış rastgele belgeler döndürür. Çözümden biraz daha az eşit olarak dağıtılır skip( random ), ancak belgelerin kaldırılması durumunda çok daha hızlı ve daha güvenli.

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

Ayrıca belgelerinize rastgele "rastgele" bir alan eklemenizi gerektirir, bu yüzden bunları oluştururken eklemeyi unutmayın: koleksiyonunuzu Geoffrey tarafından gösterildiği gibi başlatmanız gerekebilir

function addRandom(collection) { 
    collection.find().forEach(function (obj) {
        obj.random = Math.random();
        collection.save(obj);
    }); 
} 
db.eval(addRandom, db.things);

Deney sonuçları

Bu yöntem skip()(ceejayoz) yönteminden çok daha hızlıdır ve Michael tarafından bildirilen "yemek kitabı" yönteminden daha düzgün rastgele belgeler üretir:

1.000.000 element içeren bir koleksiyon için:

  • Bu yöntem makinemde milisaniyeden daha az sürüyor

  • skip()yöntem, ortalama 180 ms alır

Yemek kitabı yöntemi, çok sayıda belgenin asla seçilmemesine neden olur, çünkü rastgele sayıları onları tercih etmez.

  • Bu yöntem, zaman içinde tüm öğeleri eşit olarak seçecektir.

  • Kıyaslamamda, yemek kitabı yönteminden sadece% 30 daha yavaştı.

  • rastgelelik% 100 mükemmel değildir, ancak çok iyidir (ve gerekirse iyileştirilebilir)

Bu tarif mükemmel değil - mükemmel çözüm, diğerlerinin belirttiği gibi yerleşik bir özellik olacaktır.
Ancak, birçok amaç için iyi bir uzlaşma olmalıdır.


10

İşte için varsayılan ObjectIddeğerleri kullanmanın bir yolu _idve biraz matematik ve mantık.

// 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];

Kabuk temsilindeki genel mantık budur ve kolayca uyarlanabilir.

Yani noktalarda:

  • Koleksiyondaki minimum ve maksimum birincil anahtar değerlerini bulma

  • Bu belgelerin zaman damgaları arasına düşen rastgele bir sayı oluşturun.

  • Rasgele sayıyı minimum değere ekleyin ve bu değerden büyük veya bu değere eşit olan ilk belgeyi bulun.

Bu ObjectId, aradığımız şey olduğu için geçerli bir değer oluşturmak için "hex" içindeki zaman damgası değerinden "dolgu" kullanır . Tamsayıları _iddeğer olarak kullanmak aslında daha basit ama noktalardaki aynı temel fikirdir.


300.000.000 satırlık bir koleksiyonum var. Bu işe yarayan tek çözümdür ve yeterince hızlıdır.
Nikos

8

Python'da pymongo kullanarak:

import random

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

5
Bunu dahili olarak belirtmek gerekir ki, bu diğer cevapların çoğu gibi atlama ve sınırlama kullanacaktır.
JohnnyHK

Cevabınız doğru. Ancak, lütfen değiştirin count()ile estimated_document_count()olduğu gibi count()Mongdo v4.2 önerilmiyor.
user3848207


6

orada anahtarlanacak veri yoksa bu zordur. _id alanı nedir? onlar mongodb nesne kimlikleri mi? Eğer öyleyse, en yüksek ve en düşük değerleri elde edebilirsiniz:

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

kimliğin eşit olarak dağıtıldığını varsayarsanız (ancak değildir, ancak en azından bir başlangıçtır):

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 nasıl olurdu herhangi bir fikir? veya en azından yukarıda hangi dili kullandınız? Python mu?
Marcin

6

Python (pymongo) kullanarak toplama işlevi de çalışır.

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

Bu yaklaşım, rastgele bir sayı için bir sorgu çalıştırmaktan çok daha hızlıdır (örneğin, collection.find ([random_int]) Bu özellikle büyük koleksiyonlar için geçerlidir.


5

Rastgele bir zaman damgası seçebilir ve daha sonra oluşturulan ilk nesneyi arayabilirsiniz. Yalnızca tek bir belgeyi tarar, ancak size tekdüze bir dağıtım sağlamaz.

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

Süper doğrusal veritabanı büyümesini hesaba katmak için rasgele tarihi çarpıtmak kolayca mümkün olacaktır.
Martin Nowak

Bu çok büyük koleksiyonlar için en iyi yöntemdir, buradaki diğer çözümlerde kullanılan O (1), unline skip () veya count () 'da çalışır
marmor

4

Php benim çözüm:

/**
 * 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

Yinelenmeden belirli sayıda rastgele doküman almak için:

  1. önce tüm kimlikleri al
  2. belgelerin boyutunu almak
  3. döngü rastgele dizin alma ve yinelenen atla

    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

Yalnızca rastgele bir değer belirli bir olasılığın üzerinde olduğunda harita işlevini kullandığınız harita / azaltma kullanmanızı öneririm.

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

Harita işlevinden yalnızca bir anahtar ('1') yayıldığı için yukarıdaki reducef işlevi çalışır.

"Olasılık" değeri, mapRreduce (...) çağrılırken "kapsamda" tanımlanır.

Bunun gibi mapReduce kullanılması, parçalanmış bir db'de de kullanılabilir olmalıdır.

Eğer db'den tam olarak m belge seçmek istiyorsanız, bunu şu şekilde yapabilirsiniz:

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

Burada "countTotal" (m) db'deki belge sayısı ve "countSubset" (n) alınacak belge sayısıdır.

Bu yaklaşım, parçalanmış veritabanlarında bazı sorunlar verebilir.


4
1 öğe döndürmek için tam bir koleksiyon taraması yapmak ... bunu yapmak için en az etkili teknik olmalıdır.
Thomas

1
İşin püf noktası, rastgele sayıda rastgele öğeyi döndürmek için genel bir çözüm olmasıdır - bu durumda> 2'den fazla rastgele öğe elde edilirken diğer çözümlerden daha hızlı olacaktır.
torbenl

2

Rasgele _kimliği seçip karşılık gelen nesneyi döndürebilirsiniz:

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

Burada koleksiyondaki rastgele sayıları depolamak için yer harcamanıza gerek yok.


1

Her nesneye rastgele bir int alanı eklemenizi öneririm. Sonra sadece bir

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

rastgele bir belge seçmek için. İndex'i ({random_field: 1}) sağladığınızdan emin olun.


2
Koleksiyonunuzdaki ilk kaydın nispeten yüksek bir random_field değeri varsa, neredeyse her zaman döndürülmez mi?
thehiatus

2
thehaitus doğru, olacak - herhangi bir amaç için uygun değil
Heptic

7
Bu çözüm tamamen yanlış, rastgele bir sayı eklemek (0 a 2 ^ 32-1 arasında hayal edelim) herhangi bir iyi dağıtımı garanti etmiyor ve $ gte kullanmak rastgele seçimleriniz bile yakın olmayacağından daha da kötüleşiyor sözde rastgele bir sayıya. Bu konsepti hiç kullanmamanızı öneririm.
Maximiliano Rios

1

Benzer bir çözümle karşılaştığımda, geri izledim ve iş talebinin aslında sunulan envanterin bir tür rotasyonunu oluşturmak için olduğunu buldum. Bu durumda, MongoDB gibi veri depolarından değil, Solr gibi arama motorlarından cevapları olan çok daha iyi seçenekler vardır.

Kısacası, içeriği "akıllıca döndürme" şartıyla, tüm belgeler arasında rasgele bir sayı yerine yapmamız gereken şey, kişisel bir q skor değiştiricisini dahil etmektir. Bunu kendiniz uygulamak için, küçük bir kullanıcı popülasyonu varsayarak kullanıcı başına productId, gösterim sayısı, tıklama sayısı, son görülme tarihi ve işletmenin aq puanını hesaplamak için anlamlı bulduğu diğer faktörleri içeren bir belge saklayabilirsiniz. değiştirici. Gösterilecek seti alırken, genellikle veri deposundan son kullanıcı tarafından talep edilenden daha fazla belge talep edersiniz, sonra q skor değiştiricisini uygular, son kullanıcı tarafından istenen kayıt sayısını alırsınız, ardından sonuç sayfasını rastgele seçersiniz ayarlayın, bu nedenle belgeleri uygulama katmanındaki (bellekte) sıralayın.

Kullanıcı evreni çok büyükse, kullanıcıları kullanıcı yerine davranış gruplarına ve dizinlere göre dizine ayırabilirsiniz.

Ürün evreni yeterince küçükse, kullanıcı başına bir dizin oluşturabilirsiniz.

Bu tekniğin, yazılım çözümünü kullanma ile ilgili, değerli bir deneyim yaratmada çok daha verimli, ancak daha da önemlisi daha etkili olduğunu buldum.


1

çözümlerin hiçbiri benim için iyi sonuç vermedi. özellikle çok boşluk olduğunda ve set küçük olduğunda. Bu benim için çok iyi çalıştı (php):

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

Dili belirtiyorsunuz, fakat kullandığınız kütüphaneyi değil mi?
Benjamin

Bilginize, birinci ve üçüncü satır arasında bir belge kaldırılırsa burada bir yarış koşulu vardır. Ayrıca find+ skipoldukça kötü, sadece birini seçmek için tüm belgeleri iade ediyorsunuz: S.
Martin Konecny


1

RANDOM çözümü ile PHP / MongoDB sıralama / siparişim. Umarım bu herkese yardımcı olur.

Not: MongoDB koleksiyonumda MySQL veritabanı kaydına atıfta bulunan sayısal kimlikler var.

Önce rastgele oluşturulmuş 10 sayıdan oluşan bir dizi oluşturuyorum

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

Benim toplama içinde $ arrayElemAt ve $ mod (modül) ile birlikte $ addField boru hattı operatörü kullanın. Modül operatörü bana 0 - 9 arasında bir sayı verecektir.

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

Bundan sonra sıralı Boru Hattı'nı kullanabilirsiniz.

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

0

Basit bir kimlik anahtarınız varsa, tüm kimlikleri bir dizide saklayabilir ve ardından rastgele bir kimlik seçebilirsiniz. (Yakut cevap):

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

0

Harita / Küçült kullanarak, kesinlikle sonuçta elde ettiğiniz filtrelenmiş koleksiyonun boyutuna bağlı olarak çok etkili bir şekilde rastgele bir kayıt alabilirsiniz.

Bu yöntemi 50.000 belgeyle test ettim (filtre yaklaşık 30.000'e düşürür) ve 16GB ram ve SATA3 HDD'ye sahip bir Intel i3'te yaklaşık 400ms'de çalışır ...

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

Harita işlevi, sorgu ile eşleşen tüm belgelerin kimliklerinin bir dizisini oluşturur. Benim durumumda bunu 50.000 olası belgeden yaklaşık 30.000'i ile test ettim.

Küçült işlevi, 0 ile dizideki öğe sayısı (-1) arasında rastgele bir tamsayı seçer ve ardından _id değerini döndürür .

400 ms uzun bir süre gibi geliyor ve gerçekten de, elli bin yerine elli milyon kaydınız varsa, bu, ek yükü çok kullanıcılı durumlarda kullanılamaz hale gelene kadar artırabilir.

MongoDB'nin bu özelliği çekirdeğe dahil etmesi için açık bir sorun var ... https://jira.mongodb.org/browse/SERVER-533

Bu "rastgele" seçim, bir dizi içine ids toplamak ve daha sonra bir dizi seçmek yerine bir dizin aramasında yerleşik olsaydı, bu inanılmaz yardımcı olacaktır. (git oy ver!)


0

Bu iyi çalışır, hızlıdır, birden fazla belgeyle çalışır ve randsonunda kendini dolduracak doldurma alanı gerektirmez :

  1. koleksiyonunuzdaki .rand alanına dizin ekleyin
  2. bul ve yenile komutunu kullanın:
// 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)
  }
})

ps. Mongodb sorusunda rastgele kayıtların nasıl bulunacağı bu sorunun kopyası olarak işaretlenmiştir. Fark bu soruyu açıkça rasgele belge alma hakkında diğer biri olarak tek bir kayıt hakkında açıkça sorar olmasıdır s .


-2

Belgeden nesneye sarmalayıcı olan mongoid kullanıyorsanız, Ruby'de aşağıdakileri yapabilirsiniz. (Modelinizin Kullanıcı olduğu varsayılarak)

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

.İrbrc'mde var

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

raylar konsolunda, örneğin,

rando User
rando Article

herhangi bir koleksiyondan rastgele belge almak için.


1
Tüm koleksiyonu bir dizi halinde okuyacağı ve ardından bir kayıt seçeceği için bu çok verimsizdir.
JohnnyHK

Tamam, belki verimsiz, ama kesinlikle uygun. veri boyutunuz çok büyük değilse bunu deneyin
Zack Xu

3
Tabii, ama asıl soru 100 milyon dokümanı olan bir koleksiyon içindi, bu yüzden bu durum için çok kötü bir çözüm olurdu!
JohnnyHK

-2

sorgunuzu yürüttükten sonra shuffle-array de kullanabilirsiniz

var shuffle = requir ('shuffle dizisi');

Accounts.find (qry, işlev (err, results_array) {newIndexArr = shuffle (results_array);


-7

Verimli ve güvenilir bir şekilde çalışan şudur:

Her belgeye "rastgele" adlı bir alan ekleyin ve bu alana rastgele bir değer atayın, rastgele alan için bir dizin ekleyin ve aşağıdaki gibi ilerleyin:

Diyelim ki "bağlantılar" adı verilen bir web bağlantıları koleksiyonumuz var ve ondan rastgele bir bağlantı istiyoruz:

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

Aynı bağlantının ikinci kez açılmamasını sağlamak için, rastgele alanını yeni bir rastgele sayıyla güncelleyin:

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

2
Farklı bir rastgele anahtar seçebileceğiniz zaman neden veritabanını güncellemelisiniz ?
Jason S

Rastgele seçim yapabileceğiniz tuşların bir listesi olmayabilir.
Mike

Yani tüm koleksiyonu her seferinde sıralamak zorunda mısınız? Peki ya büyük rasgele sayılar alan şanssız kayıtlar? Asla seçilmeyeceklerdir.
Fantius

1
Bunu yapmak zorundasınız çünkü diğer çözümler, özellikle MongoDB kitabında önerilen çözümler işe yaramıyor. İlk bulma başarısız olursa, ikinci bulma daima en küçük rastgele değere sahip öğeyi döndürür. Rastgele azalan dizin oluşturursanız, ilk sorgu her zaman en büyük rasgele sayıya sahip öğeyi döndürür.
tren kazası

Her belgeye alan eklensin mi? Bence bu tavsiye edilmez.
CS_noob
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.