ฉันกำลังมองหาที่จะได้รับการบันทึกโดยการสุ่มจากขนาดใหญ่ (100 mongodb
ล้านบันทึก)
วิธีที่เร็วและมีประสิทธิภาพที่สุดในการทำคืออะไร? ข้อมูลมีอยู่แล้วและไม่มีฟิลด์ที่ฉันสามารถสร้างตัวเลขสุ่มและรับแถวสุ่ม
ข้อเสนอแนะใด ๆ
ฉันกำลังมองหาที่จะได้รับการบันทึกโดยการสุ่มจากขนาดใหญ่ (100 mongodb
ล้านบันทึก)
วิธีที่เร็วและมีประสิทธิภาพที่สุดในการทำคืออะไร? ข้อมูลมีอยู่แล้วและไม่มีฟิลด์ที่ฉันสามารถสร้างตัวเลขสุ่มและรับแถวสุ่ม
ข้อเสนอแนะใด ๆ
คำตอบ:
เริ่มต้นด้วยการเปิดตัว MongoDB 3.2 คุณสามารถรับเอกสารสุ่ม 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 อาจมีการซ้ำกันในตัวอย่างเอกสารที่ส่งคืน
ทำการนับระเบียนทั้งหมดสร้างตัวเลขสุ่มระหว่าง 0 ถึงจำนวนแล้วทำ:
db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
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
และเพิ่มอีกครั้ง
$gte
ครั้งมากขึ้น stackoverflow.com/a/9499484/79201โซลูชันทางเลือกจะทำงานได้ดีขึ้นในกรณีนี้
นอกจากนี้คุณยังสามารถใช้คุณสมบัติการทำดัชนีเชิงพื้นที่ของ 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 ของ geopoint เพื่อเพิ่มมิติการสุ่มที่สองในการสืบค้นของคุณ
สูตรต่อไปนี้ช้ากว่าโซลูชันตำราทำอาหาร mongo เล็กน้อย (เพิ่มคีย์สุ่มในทุกเอกสาร) แต่ส่งคืนเอกสารสุ่มที่กระจายอย่างเท่าเทียมกันมากขึ้น มีการกระจายน้อยกว่าskip( random )
โซลูชันเล็กน้อย แต่เร็วกว่าและปลอดภัยกว่าในกรณีที่เอกสารถูกลบ
function draw(collection, query) {
// query: mongodb query object (optional)
var query = query || { };
query['random'] = { $lte: Math.random() };
var cur = collection.find(query).sort({ rand: -1 });
if (! cur.hasNext()) {
delete query.random;
cur = collection.find(query).sort({ rand: -1 });
}
var doc = cur.next();
doc.random = Math.random();
collection.update({ _id: doc._id }, doc);
return doc;
}
นอกจากนี้คุณต้องเพิ่มฟิลด์ "สุ่ม" แบบสุ่มลงในเอกสารของคุณดังนั้นอย่าลืมเพิ่มเมื่อคุณสร้างพวกเขา: คุณอาจต้องเริ่มต้นการรวบรวมของคุณตามที่แสดงโดย Geoffrey
function addRandom(collection) {
collection.find().forEach(function (obj) {
obj.random = Math.random();
collection.save(obj);
});
}
db.eval(addRandom, db.things);
ผลการทดสอบเกณฑ์มาตรฐาน
วิธีนี้เร็วกว่ามาก skip()
วิธี (จาก ceejayoz) และสร้างเอกสารสุ่มที่สม่ำเสมอกว่าวิธี "ตำรา" ที่รายงานโดย Michael:
สำหรับคอลเลกชันที่มีองค์ประกอบ 1,000,000 รายการ:
วิธีนี้ใช้เวลาน้อยกว่าหนึ่งมิลลิวินาทีบนเครื่องของฉัน
skip()
วิธีการใช้เวลา 180 มิลลิวินาทีโดยเฉลี่ย
วิธีตำราอาหารจะทำให้เอกสารจำนวนมากไม่เคยถูกหยิบเพราะเลขสุ่มของพวกเขาไม่ชอบ
วิธีนี้จะเลือกองค์ประกอบทั้งหมดอย่างสม่ำเสมอตลอดเวลา
ในเกณฑ์มาตรฐานของฉันมันช้ากว่าวิธีตำราอาหารเพียง 30%
การสุ่มนั้นไม่สมบูรณ์แบบ 100% แต่ดีมาก (และสามารถปรับปรุงได้ถ้าจำเป็น)
สูตรนี้ไม่สมบูรณ์ - โซลูชั่นที่สมบูรณ์แบบจะมีคุณสมบัติในตัวตามที่คนอื่น ๆ ได้กล่าวไว้
อย่างไรก็ตามมันควรเป็นการประนีประนอมที่ดีสำหรับวัตถุประสงค์หลายประการ
นี่คือวิธีการใช้ค่าเริ่มต้น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];
นั่นเป็นตรรกะทั่วไปในการแทนเชลล์และปรับได้ง่าย
ดังนั้นในจุด:
ค้นหาค่าคีย์หลักขั้นต่ำและสูงสุดในคอลเลกชัน
สร้างตัวเลขสุ่มที่อยู่ระหว่างการประทับเวลาของเอกสารเหล่านั้น
เพิ่มหมายเลขสุ่มลงในค่าต่ำสุดและค้นหาเอกสารแรกที่มากกว่าหรือเท่ากับค่านั้น
สิ่งนี้ใช้ "การเติมเต็ม" จากค่าการประทับเวลาใน "hex" เพื่อสร้างObjectId
ค่าที่ถูกต้องเนื่องจากเป็นสิ่งที่เรากำลังมองหา การใช้จำนวนเต็มเป็น_id
ค่านั้นง่ายกว่า แต่เป็นแนวคิดพื้นฐานเดียวกันในประเด็น
ใน Python ใช้ pymongo:
import random
def get_random_doc():
count = collection.count()
return collection.find()[random.randrange(count)]
count()
ด้วยestimated_document_count()
ตามที่count()
เลิกใช้ใน Mongdo v4.2
มันยากถ้าไม่มีข้อมูลที่จะปิด ฟิลด์ _id คืออะไร พวกเขา mongodb วัตถุ id หรือไม่ ถ้าเป็นเช่นนั้นคุณจะได้รับค่าสูงสุดและต่ำสุด:
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);
การใช้ Python (pymongo) ฟังก์ชั่นรวมยังใช้งานได้
collection.aggregate([{'$sample': {'size': sample_size }}])
วิธีนี้เร็วกว่าการรันคิวรีสำหรับหมายเลขสุ่ม (เช่น collection.find ([random_int]) โดยเฉพาะอย่างยิ่งกรณีนี้เป็นกรณีพิเศษสำหรับคอลเลกชันขนาดใหญ่
คุณสามารถเลือกเวลาประทับแบบสุ่มและค้นหาวัตถุแรกที่สร้างขึ้นหลังจากนั้น มันจะสแกนเอกสารเพียงชุดเดียวเท่านั้นถึงแม้ว่ามันจะไม่ได้ให้การกระจายที่เหมือนกันก็ตาม
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)
};
}();
ทางออกของฉันใน 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;
}
เพื่อรับเอกสารสุ่มที่กำหนดจำนวนโดยไม่ซ้ำกัน:
วนรอบรับดัชนีแบบสุ่มและข้ามการทำซ้ำ
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)
});
});
ฉันขอแนะนำให้ใช้แผนที่ / ลดที่คุณใช้ฟังก์ชั่นแผนที่เพื่อปล่อยเฉพาะเมื่อค่าสุ่มอยู่เหนือความน่าจะเป็นที่กำหนด
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);
ฟังก์ชั่นลดการทำงานดังกล่าวข้างต้นเพราะเพียงหนึ่งปุ่ม ('1') ถูกปล่อยออกมาจากฟังก์ชั่นแผนที่
ค่าของ "ความน่าจะเป็น" ถูกกำหนดใน "ขอบเขต" เมื่อเรียกใช้ mapRreduce (... )
การใช้ mapReduce เช่นนี้ควรนำมาใช้กับ db ที่ถูกทำลาย
หากคุณต้องการเลือกเอกสารที่ตรงกับความต้องการของคุณคุณสามารถทำได้ดังนี้:
function mapf() {
if(countSubset == 0) return;
var prob = countSubset / countTotal;
if(Math.random() <= prob) {
emit(1, {"documents": [this]});
countSubset--;
}
countTotal--;
}
function reducef(key,values) {
var newArray = new Array();
for(var i=0; i < values.length; i++) {
newArray = newArray.concat(values[i].documents);
}
return {"documents": newArray};
}
res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": {"countTotal": 4, "countSubset": 2}})
printjson(res.results);
โดยที่ "countTotal" (m) คือจำนวนเอกสารใน db และ "countSubset" (n) คือจำนวนเอกสารที่จะดึง
วิธีการนี้อาจทำให้เกิดปัญหากับฐานข้อมูลที่แตกออก
คุณสามารถเลือกสุ่ม _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)
})
})
})
ที่นี่คุณไม่จำเป็นต้องใช้พื้นที่ในการจัดเก็บตัวเลขสุ่มในคอลเลกชัน
ฉันขอแนะนำให้เพิ่มฟิลด์ int แบบสุ่มให้กับแต่ละวัตถุ จากนั้นคุณสามารถทำ
findOne({random_field: {$gte: rand()}})
เพื่อเลือกเอกสารแบบสุ่ม ตรวจสอบให้แน่ใจว่าคุณมั่นใจในดัชนี ({random_field: 1})
เมื่อฉันเผชิญกับวิธีแก้ปัญหาที่คล้ายกันฉันย้อนรอยและพบว่าคำขอทางธุรกิจนั้นจริง ๆ แล้วสำหรับการสร้างรูปแบบการหมุนของสินค้าคงคลังที่นำเสนอ ในกรณีนี้มีตัวเลือกที่ดีกว่ามากซึ่งมีคำตอบจากเครื่องมือค้นหาอย่าง Solr ไม่ใช่ที่เก็บข้อมูลอย่าง MongoDB
กล่าวโดยย่อเกี่ยวกับความต้องการในการ "หมุนเวียนเนื้อหาอย่างชาญฉลาด" สิ่งที่เราควรทำแทนการใช้หมายเลขสุ่มในเอกสารทั้งหมดคือการรวมเครื่องมือปรับคะแนน q ส่วนตัว ในการดำเนินการนี้ด้วยตนเองโดยสมมติว่ามีผู้ใช้จำนวนน้อยคุณสามารถจัดเก็บเอกสารต่อผู้ใช้ที่มีรหัสผลิตภัณฑ์จำนวนการแสดงผลจำนวนการคลิกผ่านวันที่เห็นล่าสุดและปัจจัยอื่น ๆ ที่ธุรกิจพบว่ามีความหมายในการคำนวณคะแนน aq ส่วนขยาย เมื่อดึงชุดที่จะแสดงโดยทั่วไปแล้วคุณร้องขอเอกสารเพิ่มเติมจากแหล่งข้อมูลมากกว่าที่ผู้ใช้ร้องขอจากนั้นใช้ตัวปรับคะแนน q ใช้จำนวนระเบียนที่ร้องขอโดยผู้ใช้ปลายทางจากนั้นสุ่มหน้าผลลัพธ์เล็ก ๆ ตั้งค่าดังนั้นเพียงเรียงลำดับเอกสารในเลเยอร์แอปพลิเคชัน (ในหน่วยความจำ)
หากจักรวาลของผู้ใช้มีขนาดใหญ่เกินไปคุณสามารถจัดหมวดหมู่ผู้ใช้เป็นกลุ่มพฤติกรรมและดัชนีโดยกลุ่มพฤติกรรมแทนที่จะเป็นผู้ใช้
หากจักรวาลของผลิตภัณฑ์มีขนาดเล็กเพียงพอคุณสามารถสร้างดัชนีต่อผู้ใช้
ฉันพบว่าเทคนิคนี้มีประสิทธิภาพมากขึ้น แต่ที่สำคัญกว่านั้นมีประสิทธิภาพมากกว่าในการสร้างประสบการณ์ที่เกี่ยวข้องและคุ้มค่าในการใช้โซลูชันซอฟต์แวร์
ไม่ใช่วิธีแก้ปัญหาทำงานได้ดีสำหรับฉัน โดยเฉพาะอย่างยิ่งเมื่อมีช่องว่างจำนวนมากและชุดเล็ก นี้ทำงานได้ดีมากสำหรับฉัน (ใน php):
$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();
find
+ ยังskip
ไม่ดีคุณกำลังส่งคืนเอกสารทั้งหมดเพื่อเลือก: S
หากคุณใช้พังพอนแล้วคุณอาจใช้พังพอนสุ่มสุ่ม พังพอน
PHP / MongoDB จัดเรียง / เรียงลำดับตาม RANDOM ของฉัน หวังว่านี่จะช่วยทุกคน
หมายเหตุ: ฉันมี ID ตัวเลขภายในคอลเลกชัน MongoDB ของฉันที่อ้างถึงระเบียนฐานข้อมูล MySQL
ก่อนอื่นฉันจะสร้างอาร์เรย์ที่มีตัวเลขสุ่ม 10 ตัว
$randomNumbers = [];
for($i = 0; $i < 10; $i++){
$randomNumbers[] = rand(0,1000);
}
ในการรวมตัวของฉันฉันใช้ตัวดำเนินการไปป์ไลน์ $ addField รวมกับ $ arrayElemAt และ $ mod (โมดูลัส) ตัวดำเนินการโมดูลัสจะให้ตัวเลขจาก 0 - 9 ซึ่งฉันใช้เพื่อเลือกตัวเลขจากอาร์เรย์ด้วยตัวเลขที่สร้างแบบสุ่ม
$aggregate[] = [
'$addFields' => [
'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
],
];
หลังจากนั้นคุณสามารถใช้ Pipeline เรียงลำดับ
$aggregate[] = [
'$sort' => [
'random_sort' => 1
]
];
หากคุณมีรหัสประจำตัวที่เรียบง่ายคุณสามารถเก็บรหัสทั้งหมดไว้ในอาร์เรย์จากนั้นเลือกรหัสสุ่ม (คำตอบทับทิม):
ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first
การใช้แผนที่ / ลดขนาดคุณสามารถได้รับการบันทึกแบบสุ่มไม่จำเป็นต้องมีประสิทธิภาพมากนักทั้งนี้ขึ้นอยู่กับขนาดของคอลเลกชันที่กรองแล้วซึ่งคุณได้ทำงานด้วย
ฉันได้ทดสอบวิธีการนี้กับเอกสาร 50,000 ฉบับ (ตัวกรองลดไปเป็นประมาณ 30,000) และดำเนินการในประมาณ400msบน Intel i3 ที่มี 16GB ram และ SATA3 HDD ...
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" }
}
);
ฟังก์ชั่นแผนที่สร้างอาร์เรย์ของรหัสทั้งหมดของเอกสารที่ตรงกับแบบสอบถาม ในกรณีของฉันฉันทดสอบกับเอกสารที่เป็นไปได้ประมาณ 30,000 ฉบับจาก 50,000 ฉบับ
ฟังก์ชั่นลดเพียงแค่เลือกจำนวนเต็มแบบสุ่มระหว่าง 0 และจำนวนของรายการ (-1) ในอาร์เรย์แล้วส่งกลับ_id ที่จากอาร์เรย์
400ms ฟังดูนานและเป็นจริงถ้าคุณมีห้าสิบล้านระเบียนแทนที่จะเป็นห้าหมื่นนี่อาจเพิ่มค่าใช้จ่ายจนถึงจุดที่ไม่สามารถใช้งานได้ในสถานการณ์ที่ผู้ใช้หลายคน
มีปัญหาแบบเปิดสำหรับ MongoDB ที่จะรวมคุณสมบัตินี้ไว้ในหลัก ... https://jira.mongodb.org/browse/SERVER-533
หากการเลือกแบบ "สุ่ม" นี้ถูกสร้างขึ้นในการค้นหาดัชนีแทนที่จะรวบรวมรหัสลงในอาร์เรย์แล้วเลือกหนึ่งรายการสิ่งนี้จะช่วยได้อย่างไม่น่าเชื่อ (ไปโหวตเลย!)
วิธีนี้ใช้งานได้ดีรวดเร็วใช้งานได้กับเอกสารหลายฉบับและไม่จำเป็นต้องมีrand
ฟิลด์การเติมข้อมูล
// 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ถูกทำเครื่องหมายว่าซ้ำกับคำถามนี้ ความแตกต่างคือว่าคำถามนี้ถามอย่างชัดเจนเกี่ยวกับการบันทึกเดียวเป็นอีกหนึ่งอย่างชัดเจนเกี่ยวกับการรับเอกสารสุ่มs
หากคุณใช้ 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
เพื่อรับเอกสารแบบสุ่มจากการรวบรวมใด ๆ
คุณสามารถใช้อาเรย์สับเปลี่ยนหลังจากเรียกใช้คิวรีของคุณ
var shuffle = ต้องการ ('shuffle-array');
Accounts.find (qry, function (err, results_array) {newIndexArr = shuffle (results_array);
สิ่งนี้ทำงานได้อย่างมีประสิทธิภาพและเชื่อถือได้คือ:
เพิ่มเขตข้อมูลที่เรียกว่า "สุ่ม" ให้กับแต่ละเอกสารและกำหนดค่าสุ่มให้กับเพิ่มดัชนีสำหรับเขตข้อมูลสุ่มและดำเนินการดังนี้:
สมมติว่าเรามีคอลเลกชันของเว็บลิงค์ที่เรียกว่า "ลิงค์" และเราต้องการลิงค์สุ่มจากมัน:
link = db.links.find().sort({random: 1}).limit(1)[0]
เพื่อให้แน่ใจว่าลิงก์เดิมจะไม่ปรากฏขึ้นเป็นครั้งที่สองให้อัปเดตฟิลด์สุ่มด้วยหมายเลขสุ่มใหม่:
db.links.update({random: Math.random()}, link)