Catatan acak dari MongoDB


336

Saya mencari untuk mendapatkan catatan acak dari yang sangat besar (rekor 100 juta) mongodb.

Apa cara tercepat dan paling efisien untuk melakukannya? Data sudah ada di sana dan tidak ada bidang di mana saya bisa menghasilkan angka acak dan mendapatkan baris acak.

Ada saran?


2
Lihat juga pertanyaan SO ini berjudul "Memesan hasil yang ditetapkan secara acak di mongo" . Berpikir tentang memesan secara acak kumpulan hasil adalah versi yang lebih umum dari pertanyaan ini - lebih kuat dan lebih bermanfaat.
David J.

11
Pertanyaan ini terus bermunculan. Informasi terbaru kemungkinan dapat ditemukan di permintaan fitur untuk mendapatkan item acak dari koleksi di pelacak tiket MongoDB. Jika diterapkan secara asli, itu kemungkinan akan menjadi opsi yang paling efisien. (Jika Anda menginginkan fitur tersebut, lanjutkan memilihnya.)
David J.

Apakah ini koleksi sharded?
Dylan Tong

3
Jawaban yang benar telah diberikan oleh @JohnnyHK di bawah ini: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian

Apakah ada yang tahu seberapa lambat ini daripada hanya mengambil catatan pertama? Saya berdebat apakah perlu mengambil sampel acak untuk melakukan sesuatu vs hanya melakukannya secara berurutan.
David Kong

Jawaban:


248

Dimulai dengan rilis 3.2 MongoDB, Anda bisa mendapatkan N dokumen acak dari koleksi menggunakan $sampleoperator pipa agregasi:

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

Jika Anda ingin memilih dokumen acak dari subset koleksi yang difilter, tambahkan $matchtahapan ke pipeline:

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

Seperti disebutkan dalam komentar, ketika sizelebih besar dari 1, mungkin ada duplikat dalam sampel dokumen yang dikembalikan.


12
Ini adalah cara yang baik, tetapi ingat bahwa itu TIDAK menjamin bahwa tidak ada salinan dari objek yang sama dalam sampel.
Matheus Araujo

10
@MatheusAraujo yang tidak masalah jika Anda menginginkan satu catatan tetapi tetap bagus
Toby

3
Bukan untuk menjadi bertele-tele tetapi pertanyaannya tidak menentukan versi MongoDB, jadi saya berasumsi memiliki versi terbaru adalah masuk akal.
dalanmiller

2
@Nepoxx Lihat dokumen tentang pemrosesan yang terlibat.
JohnnyHK

2
@ brycejl Itu akan memiliki kesalahan fatal tidak cocok apa pun jika $ sample stage tidak memilih dokumen yang cocok.
JohnnyHK

115

Lakukan penghitungan semua catatan, hasilkan angka acak antara 0 dan hitungan, lalu lakukan:

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

139
Sayangnya melewatkan () agak tidak efisien karena harus memindai banyak dokumen. Juga, ada kondisi balapan jika baris dihapus antara mendapatkan hitungan dan menjalankan kueri.
mstearn

6
Perhatikan bahwa angka acak harus antara 0 dan jumlah (eksklusif). Yaitu, jika Anda memiliki 10 item, angka acak harus antara 0 dan 9. Jika tidak kursor dapat mencoba melewati item terakhir, dan tidak ada yang akan dikembalikan.
matt

4
Terima kasih, bekerja dengan baik untuk tujuan saya. @mstearn, komentar Anda tentang efisiensi dan kondisi ras valid, tetapi untuk koleksi yang tidak penting (ekstrak batch sisi server satu kali dalam koleksi di mana catatan tidak dihapus), ini jauh lebih unggul daripada hacky (IMO) solusi dalam Mongo Cookbook.
Michael Moussa

4
apa pengaturan batas untuk -1 lakukan?
MonkeyBonkey

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Jika numberToReturn adalah 0, db akan menggunakan ukuran pengembalian default. Jika angkanya negatif, maka basis data akan mengembalikan nomor itu dan menutup kursor. "
ceejayoz

86

Pembaruan untuk MongoDB 3.2

3,2 memperkenalkan $ sampel ke pipa agregasi.

Ada juga posting blog yang bagus untuk mempraktikkannya.

Untuk versi yang lebih lama (jawaban sebelumnya)

Ini sebenarnya adalah permintaan fitur: http://jira.mongodb.org/browse/SERVER-533 tetapi diajukan di bawah "Tidak akan diperbaiki."

Cookbook memiliki resep yang sangat baik untuk memilih dokumen acak dari koleksi: http://cookbook.mongodb.org/patterns/random-attribute/

Untuk memparafrasekan resep, Anda menetapkan nomor acak ke dokumen Anda:

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

Kemudian pilih dokumen acak:

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

Permintaan dengan keduanya $gtedan $lteperlu untuk menemukan dokumen dengan nomor acak terdekatrand .

Dan tentu saja Anda ingin mengindeks pada bidang acak:

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

Jika Anda sudah menanyakan indeks, cukup jatuhkan, tambahkan random: 1, dan tambahkan lagi.


7
Dan di sini adalah cara sederhana untuk menambahkan bidang acak ke setiap dokumen dalam koleksi. function setRandom () {db.topics.find (). forEach (fungsi (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey

8
Ini memilih dokumen secara acak, tetapi jika Anda melakukannya lebih dari sekali, pencarian tidak independen. Anda lebih cenderung mendapatkan dokumen yang sama dua kali berturut-turut daripada yang ditentukan oleh kesempatan acak.
kurang

12
Sepertinya implementasi hashing sirkular yang buruk. Ini bahkan lebih buruk daripada yang kurang dikatakan: bahkan satu pencarian bias karena angka acak tidak terdistribusi secara merata. Untuk melakukan ini dengan benar, Anda perlu satu set, katakanlah, 10 angka acak per dokumen. Semakin banyak angka acak yang Anda gunakan per dokumen, semakin seragam distribusi keluaran menjadi.
Thomas

4
Tiket MongoDB JIRA masih hidup: jira.mongodb.org/browse/SERVER-533 Pergi komentar dan pilih jika Anda menginginkan fitur.
David J.

1
Perhatikan jenis peringatan yang disebutkan. Ini tidak bekerja secara efisien dengan sejumlah kecil dokumen. Diberikan dua item dengan kunci acak 3 dan 63. Dokumen # 63 akan dipilih lebih sering di mana $gteyang pertama. Solusi alternatif stackoverflow.com/a/9499484/79201 akan bekerja lebih baik dalam kasus ini.
Ryan Schumacher

56

Anda juga dapat menggunakan fitur pengindeksan geospasial MongoDB untuk memilih dokumen 'terdekat' ke nomor acak.

Pertama, aktifkan pengindeksan geospasial pada koleksi:

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

Untuk membuat banyak dokumen dengan titik acak pada sumbu X:

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

Maka Anda bisa mendapatkan dokumen acak dari koleksi seperti ini:

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

Atau Anda dapat mengambil beberapa dokumen terdekat dari titik acak:

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

Ini hanya membutuhkan satu permintaan dan tidak ada pemeriksaan nol, ditambah kodenya bersih, sederhana dan fleksibel. Anda bahkan bisa menggunakan sumbu Y dari geopoint untuk menambahkan dimensi keacakan kedua ke kueri Anda.


8
Saya suka jawaban ini, Ini yang paling efisien yang pernah saya lihat yang tidak memerlukan banyak masalah tentang sisi server.
Tony Million

4
Ini juga bias terhadap dokumen yang kebetulan memiliki beberapa poin di sekitarnya.
Thomas

6
Itu benar, dan ada masalah lain juga: dokumen sangat berkorelasi pada kunci acak mereka, sehingga sangat dapat diprediksi dokumen mana yang akan dikembalikan sebagai grup jika Anda memilih banyak dokumen. Selain itu, dokumen yang dekat dengan batas (0 dan 1) cenderung tidak dipilih. Yang terakhir dapat diselesaikan dengan menggunakan geomapping bola, yang membungkus di tepi. Namun, Anda harus melihat jawaban ini sebagai versi resep resep buku masak yang disempurnakan, bukan sebagai mekanisme pemilihan acak yang sempurna. Cukup acak untuk sebagian besar tujuan.
Nico de Poel

@NicodePoel, saya suka jawaban Anda serta komentar Anda! Dan saya punya beberapa pertanyaan untuk Anda: 1- Bagaimana Anda tahu bahwa poin yang dekat dengan batas 0 dan 1 lebih kecil kemungkinannya untuk dipilih, apakah itu didasarkan pada beberapa dasar matematika?, 2- Dapatkah Anda menguraikan lebih lanjut tentang geomapping bola, bagaimana akan lebih baik pemilihan acak, dan bagaimana melakukannya di MongoDB? ... Dihormati!
securecurve

Perkaya ide Anda. Akhirnya, saya memiliki kode hebat yang ramah CPU & RAM! Terima kasih
Qais Bsharat

21

Resep berikut ini sedikit lebih lambat daripada solusi buku masak mongo (tambahkan kunci acak pada setiap dokumen), tetapi mengembalikan dokumen acak yang didistribusikan lebih merata. Ini sedikit kurang merata daripada skip( random )solusi, tetapi jauh lebih cepat dan lebih aman jika dokumen dihapus.

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

Ini juga mengharuskan Anda untuk menambahkan bidang "acak" acak ke dokumen Anda jadi jangan lupa untuk menambahkan ini saat Anda membuatnya: Anda mungkin perlu menginisialisasi koleksi Anda seperti yang ditunjukkan oleh Geoffrey

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

Hasil benchmark

Metode ini jauh lebih cepat daripada skip()metode (ceejayoz) dan menghasilkan lebih banyak dokumen acak yang seragam daripada metode "buku masak" yang dilaporkan oleh Michael:

Untuk koleksi dengan 1.000.000 elemen:

  • Metode ini memakan waktu kurang dari satu milidetik pada mesin saya

  • yang skip()metode mengambil 180 ms rata-rata

Metode buku masak akan menyebabkan sejumlah besar dokumen tidak dapat dipilih karena jumlah acak mereka tidak disukai.

  • Metode ini akan memilih semua elemen secara merata dari waktu ke waktu.

  • Dalam tolok ukur saya, itu hanya 30% lebih lambat dari metode buku resep.

  • keacakannya tidak 100% sempurna tetapi sangat bagus (dan itu dapat ditingkatkan jika perlu)

Resep ini tidak sempurna - solusi sempurna akan menjadi fitur bawaan seperti yang telah dicatat orang lain.
Namun itu harus menjadi kompromi yang baik untuk banyak tujuan.


10

Berikut adalah cara menggunakan nilai default ObjectIduntuk _iddan sedikit matematika dan logika.

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

Itulah logika umum dalam representasi shell dan mudah beradaptasi.

Jadi dalam poin:

  • Temukan nilai kunci utama minimum dan maksimum dalam koleksi

  • Hasilkan nomor acak yang berada di antara cap waktu dari dokumen-dokumen itu.

  • Tambahkan angka acak ke nilai minimum dan temukan dokumen pertama yang lebih besar atau sama dengan nilai itu.

Ini menggunakan "padding" dari nilai timestamp di "hex" untuk membentuk ObjectIdnilai yang valid karena itulah yang kami cari. Menggunakan bilangan bulat sebagai _idnilai pada dasarnya lebih sederhana tetapi ide dasar yang sama dalam poin.


Saya memiliki koleksi 300 000 000 baris. Ini adalah satu-satunya solusi yang berfungsi dan cukup cepat.
Nikos

8

Dalam Python menggunakan pymongo:

import random

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

5
Patut dicatat bahwa secara internal, ini akan menggunakan lewati dan batasi, seperti banyak jawaban lainnya.
JohnnyHK

Jawaban Anda benar. Namun, silahkan ganti count()dengan estimated_document_count()yang count()sudah ditinggalkan di Mongdo v4.2.
user3848207

8

Sekarang Anda bisa menggunakan agregat. Contoh:

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

Lihat dokumen .


3
Catatan: $ sample dapat memperoleh dokumen yang sama lebih dari satu kali
Saman Shafigh

6

sulit jika tidak ada data di sana untuk dikunci. apa bidang _id? apakah mereka mongodb objek id? Jika demikian, Anda bisa mendapatkan nilai tertinggi dan terendah:

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

maka jika Anda menganggap id didistribusikan secara seragam (tetapi tidak, tapi setidaknya ini awal):

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
Adakah ide seperti apa itu di PHP? atau setidaknya bahasa apa yang Anda gunakan di atas? apakah itu Python?
Marcin

6

Menggunakan Python (pymongo), fungsi agregat juga berfungsi.

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

Pendekatan ini jauh lebih cepat daripada menjalankan kueri untuk nomor acak (mis. Collection.find ([random_int]). Ini khususnya kasus untuk koleksi besar.


5

Anda dapat memilih cap waktu acak dan mencari objek pertama yang dibuat sesudahnya. Itu hanya akan memindai satu dokumen, meskipun itu tidak selalu memberi Anda distribusi yang seragam.

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

Akan mudah untuk mengubah tanggal acak untuk memperhitungkan pertumbuhan basis data superlinear.
Martin Nowak

ini adalah metode terbaik untuk koleksi yang sangat besar, ini bekerja di O (1), lompati lompatan () atau hitung () yang digunakan dalam solusi lain di sini
marmor

4

Solusi saya di php:

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}

3

Untuk mendapatkan jumlah dokumen acak yang ditentukan tanpa duplikat:

  1. pertama-tama dapatkan semua id
  2. dapatkan ukuran dokumen
  3. loop mendapatkan indeks acak dan lewati duplikat

    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

Saya akan menyarankan menggunakan peta / mengurangi, di mana Anda menggunakan fungsi peta hanya memancarkan ketika nilai acak di atas probabilitas yang diberikan.

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

Fungsi pengurangan di atas berfungsi karena hanya satu kunci ('1') yang dipancarkan dari fungsi peta.

Nilai "probabilitas" didefinisikan dalam "lingkup", ketika memohon mapRreduce (...)

Menggunakan mapReduce seperti ini juga bisa digunakan pada db yang di-shard.

Jika Anda ingin memilih dengan tepat dari dokumen dari db, Anda dapat melakukannya seperti ini:

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

Di mana "countTotal" (m) adalah jumlah dokumen dalam db, dan "countSubset" (n) adalah jumlah dokumen yang akan diambil.

Pendekatan ini mungkin memberikan beberapa masalah pada basis data sharded.


4
Melakukan pemindaian koleksi penuh untuk mengembalikan 1 elemen ... ini harus menjadi teknik yang paling tidak efisien untuk melakukannya.
Thomas

1
Kuncinya adalah, bahwa itu adalah solusi umum untuk mengembalikan jumlah acak elemen - dalam hal ini akan lebih cepat daripada solusi lain ketika mendapatkan> 2 elemen acak.
torbenl

2

Anda dapat memilih _id acak dan mengembalikan objek yang sesuai:

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

Di sini Anda tidak perlu menghabiskan ruang untuk menyimpan nomor acak dalam koleksi.


1

Saya sarankan menambahkan bidang int acak ke setiap objek. Maka Anda bisa melakukan a

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

untuk memilih dokumen acak. Pastikan Anda memastikanIndex ({random_field: 1})


2
Jika catatan pertama dalam koleksi Anda memiliki nilai random_field yang relatif tinggi, bukankah akan dikembalikan hampir sepanjang waktu?
thehiatus

2
thehaitus benar, itu akan - tidak cocok untuk tujuan apa pun
Heptic

7
Solusi ini benar-benar salah, menambahkan angka acak (mari kita bayangkan di antara 0 a 2 ^ 32-1) tidak menjamin distribusi yang baik dan menggunakan $ gte menjadikannya lebih buruk, karena pemilihan acak Anda tidak akan lebih dekat ke nomor pseudo-acak. Saya sarankan untuk tidak menggunakan konsep ini.
Maximiliano Rios

1

Ketika saya dihadapkan dengan solusi yang serupa, saya mundur dan menemukan bahwa permintaan bisnis sebenarnya untuk menciptakan beberapa bentuk rotasi inventaris yang disajikan. Dalam hal ini, ada opsi yang jauh lebih baik, yang memiliki jawaban dari mesin pencari seperti Solr, bukan toko data seperti MongoDB.

Singkatnya, dengan persyaratan untuk "memutar secara cerdas" konten, apa yang harus kita lakukan alih-alih nomor acak di semua dokumen adalah memasukkan pengubah skor q pribadi. Untuk menerapkan ini sendiri, dengan asumsi populasi kecil pengguna, Anda dapat menyimpan dokumen per pengguna yang memiliki productId, jumlah tayangan, jumlah klik per tayang, tanggal terakhir terlihat, dan apa pun faktor lain yang ditemukan bisnis yang berarti untuk menghitung skor aq pengubah. Saat mengambil set untuk ditampilkan, biasanya Anda meminta lebih banyak dokumen dari penyimpanan data daripada yang diminta oleh pengguna akhir, kemudian menerapkan pengubah skor q, mengambil jumlah catatan yang diminta oleh pengguna akhir, lalu mengacak halaman hasil, sedikit mengatur, jadi cukup mengurutkan dokumen dalam lapisan aplikasi (dalam memori).

Jika semesta pengguna terlalu besar, Anda dapat mengategorikan pengguna ke dalam kelompok perilaku dan indeks berdasarkan kelompok perilaku daripada pengguna.

Jika semesta produk cukup kecil, Anda dapat membuat indeks per pengguna.

Saya menemukan teknik ini jauh lebih efisien, tetapi yang lebih penting lebih efektif dalam menciptakan pengalaman yang relevan dan bermanfaat dalam menggunakan solusi perangkat lunak.


1

tidak ada solusi yang bekerja dengan baik untuk saya. terutama ketika ada banyak celah dan set kecil. ini bekerja sangat baik untuk saya (dalam php):

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

Anda menentukan bahasa, tetapi bukan perpustakaan yang Anda gunakan?
Benjamin

FYI, ada kondisi lomba di sini jika dokumen dihapus antara baris pertama dan ketiga. Juga find+ skipsangat buruk, Anda mengembalikan semua dokumen hanya untuk memilih satu: S.
Martin Konecny


1

PHP / MongoDB saya mengurutkan / memesan dengan solusi RANDOM. Semoga ini bisa membantu siapa saja.

Catatan: Saya memiliki ID numerik dalam koleksi MongoDB saya yang merujuk ke catatan database MySQL.

Pertama saya membuat array dengan 10 angka yang dihasilkan secara acak

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

Dalam agregasi saya, saya menggunakan operator pipa $ addField yang dikombinasikan dengan $ arrayElemAt dan $ mod (modulus). Operator modulus akan memberi saya angka dari 0 - 9 yang kemudian saya gunakan untuk memilih angka dari array dengan angka yang dihasilkan secara acak.

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

Setelah itu Anda bisa menggunakan semacam Pipeline.

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

0

Jika Anda memiliki kunci id sederhana, Anda bisa menyimpan semua id dalam sebuah array, dan kemudian memilih id acak. (Jawaban Ruby):

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

0

Menggunakan Map / Reduce, Anda tentu bisa mendapatkan catatan acak, hanya saja tidak harus sangat efisien tergantung pada ukuran koleksi yang difilter yang Anda akhirnya bekerja dengan.

Saya telah menguji metode ini dengan 50.000 dokumen (filter menguranginya menjadi sekitar 30.000), dan dijalankan dalam sekitar 400ms pada Intel i3 dengan ram 16GB dan HDD 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" }
    }
);

Fungsi Peta hanya membuat array id dari semua dokumen yang cocok dengan kueri. Dalam kasus saya, saya menguji ini dengan sekitar 30.000 dari 50.000 dokumen yang mungkin.

Fungsi Reduce hanya mengambil integer acak antara 0 dan jumlah item (-1) dalam array, dan kemudian mengembalikan _id itu dari array.

400ms kedengarannya seperti waktu yang lama, dan memang benar, jika Anda memiliki lima puluh juta rekaman, bukan lima puluh ribu, ini dapat meningkatkan overhead ke titik di mana ia menjadi tidak dapat digunakan dalam situasi multi-pengguna.

Ada masalah terbuka untuk MongoDB untuk memasukkan fitur ini dalam inti ... https://jira.mongodb.org/browse/SERVER-533

Jika pilihan "acak" ini dibangun ke dalam pencarian indeks alih-alih mengumpulkan id ke dalam array dan kemudian memilihnya, ini akan sangat membantu. (pilih itu!)


0

Ini berfungsi dengan baik, cepat, bekerja dengan banyak dokumen dan tidak memerlukan randbidang isian, yang pada akhirnya akan mengisi sendiri:

  1. tambahkan indeks ke bidang .rand pada koleksi Anda
  2. gunakan temukan dan segarkan, sesuatu seperti:
// 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. Cara menemukan catatan acak dalam pertanyaan mongodb ditandai sebagai duplikat dari pertanyaan ini. Perbedaannya adalah bahwa pertanyaan ini menanyakan secara eksplisit tentang catatan tunggal seperti yang lain secara eksplisit tentang mendapatkan acak dokumen s .


-2

Jika Anda menggunakan mongoid, pembungkus dokumen-ke-objek, Anda dapat melakukan hal berikut di Ruby. (Dengan asumsi model Anda adalah Pengguna)

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

Di .irbrc saya, saya punya

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

jadi di konsol rel, saya bisa lakukan, misalnya,

rando User
rando Article

untuk mendapatkan dokumen secara acak dari koleksi apa pun.


1
Ini sangat tidak efisien karena akan membaca seluruh koleksi menjadi sebuah array dan kemudian mengambil satu catatan.
JohnnyHK

Ok, mungkin tidak efisien, tapi pasti nyaman. coba ini jika ukuran data Anda tidak terlalu besar
Zack Xu

3
Tentu, tetapi pertanyaan awal adalah untuk koleksi dengan 100 juta dokumen jadi ini akan menjadi solusi yang sangat buruk untuk kasus itu!
JohnnyHK

-2

Anda juga dapat menggunakan shuffle-array setelah mengeksekusi kueri Anda

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

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


-7

Apa yang bekerja secara efisien dan andal adalah ini:

Tambahkan bidang yang disebut "acak" untuk setiap dokumen dan berikan nilai acak padanya, tambahkan indeks untuk bidang acak dan lanjutkan sebagai berikut:

Anggaplah kita memiliki koleksi tautan web yang disebut "tautan" dan kami ingin tautan acak darinya:

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

Untuk memastikan tautan yang sama tidak akan muncul lagi, perbarui bidang acaknya dengan nomor acak baru:

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

2
mengapa memperbarui database ketika Anda bisa memilih kunci acak yang berbeda?
Jason S

Anda mungkin tidak memiliki daftar tombol untuk dipilih secara acak.
Mike

Jadi, Anda harus mengurutkan seluruh koleksi setiap kali? Dan bagaimana dengan catatan sial yang mendapat angka acak besar? Mereka tidak akan pernah dipilih.
Fantius

1
Anda harus melakukan ini karena solusi lain, terutama yang disarankan dalam buku MongoDB, tidak berfungsi. Jika penemuan pertama gagal, penemuan kedua selalu mengembalikan item dengan nilai acak terkecil. Jika Anda mengindeks acak secara turun-naik, kueri pertama selalu mengembalikan item dengan angka acak terbesar.
trainwreck

Menambahkan bidang di setiap dokumen? Saya pikir itu tidak disarankan.
CS_noob
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.