บันทึกแบบสุ่มจาก MongoDB


336

ฉันกำลังมองหาที่จะได้รับการบันทึกโดยการสุ่มจากขนาดใหญ่ (100 mongodbล้านบันทึก)

วิธีที่เร็วและมีประสิทธิภาพที่สุดในการทำคืออะไร? ข้อมูลมีอยู่แล้วและไม่มีฟิลด์ที่ฉันสามารถสร้างตัวเลขสุ่มและรับแถวสุ่ม

ข้อเสนอแนะใด ๆ


2
ดูเพิ่มเติมนี้คำถาม SO หัวข้อ "การสั่งซื้อชุดผลลัพธ์แบบสุ่มใน Mongo" การคิดถึงการเรียงลำดับชุดผลลัพธ์แบบสุ่มเป็นคำถามทั่วไปที่มากขึ้น - มีประสิทธิภาพและมีประโยชน์มากกว่า
David J.

11
คำถามนี้ยังคงโผล่ขึ้นมา สามารถดูข้อมูลล่าสุดได้ที่คำขอคุณลักษณะเพื่อรับรายการแบบสุ่มจากการรวบรวมในตัวติดตามตั๋ว MongoDB หากมีการใช้งานตามปกติอาจเป็นตัวเลือกที่มีประสิทธิภาพ (หากคุณต้องการคุณสมบัติให้ลงคะแนนเลย)
David J.

นี่เป็นคอลเล็กชั่นเศษหรือไม่
Dylan Tong

3
คำตอบที่ถูกต้องได้รับจาก @JohnnyHK ด้านล่าง: db.mycoll.aggregate ({$ ตัวอย่าง: {ขนาด: 1}})
Florian

มีใครรู้บ้างไหมว่านี่มันช้ากว่าการบันทึกครั้งแรกไหม? ฉันกำลังถกเถียงกันว่ามันคุ้มค่าหรือเปล่าที่จะสุ่มตัวอย่างเพื่อทำบางสิ่งบางอย่างเทียบกับเพียงแค่ทำตามลำดับ
David Kong

คำตอบ:


248

เริ่มต้นด้วยการเปิดตัว 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 อาจมีการซ้ำกันในตัวอย่างเอกสารที่ส่งคืน


12
นี่เป็นวิธีที่ดี แต่โปรดจำไว้ว่าอย่ารับประกันว่าไม่มีสำเนาของวัตถุเดียวกันในตัวอย่าง
Matheus Araujo

10
@MatheusAraujo ซึ่งจะไม่สำคัญว่าถ้าคุณต้องการหนึ่งระเบียน แต่จุดที่ดีอยู่แล้ว
Toby

3
ไม่ใช่เรื่องอวดดี แต่คำถามไม่ได้ระบุรุ่น MongoDB ดังนั้นฉันคิดว่าการมีเวอร์ชั่นล่าสุดนั้นสมเหตุสมผล
dalanmiller

2
@Nepoxx ดูเอกสารที่เกี่ยวข้องกับการประมวลผลที่เกี่ยวข้อง
JohnnyHK

2
@brycejl ที่จะมีข้อบกพร่องร้ายแรงของการจับคู่อะไรถ้าขั้นตอน $ ตัวอย่างไม่ได้เลือกเอกสารที่ตรงกัน
JohnnyHK

115

ทำการนับระเบียนทั้งหมดสร้างตัวเลขสุ่มระหว่าง 0 ถึงจำนวนแล้วทำ:

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

139
ขออภัย skip () ค่อนข้างไม่มีประสิทธิภาพเนื่องจากต้องสแกนเอกสารจำนวนมาก นอกจากนี้ยังมีสภาพการแข่งขันหากแถวถูกลบระหว่างการนับและเรียกใช้แบบสอบถาม
mstearn

6
โปรดทราบว่าตัวเลขสุ่มควรอยู่ระหว่าง 0 และนับ (พิเศษ) นั่นคือถ้าคุณมี 10 รายการหมายเลขสุ่มควรอยู่ระหว่าง 0 ถึง 9 ไม่เช่นนั้นเคอร์เซอร์อาจพยายามข้ามรายการสุดท้ายและไม่มีอะไรจะถูกส่งคืน
ด้าน

4
ขอบคุณทำงานอย่างสมบูรณ์แบบสำหรับวัตถุประสงค์ของฉัน @mearearn ความคิดเห็นของคุณเกี่ยวกับประสิทธิภาพและสภาพการแข่งขันมีผลใช้ได้ แต่สำหรับคอลเลกชันที่ไม่สำคัญ (การแยกแบทช์ฝั่งเซิร์ฟเวอร์ครั้งเดียวในการรวบรวมที่บันทึกไม่ถูกลบ) นี่ยิ่งใหญ่กว่าแฮ็ค (IMO) วิธีแก้ปัญหาใน Mongo Cookbook
Michael Moussa

4
การตั้งค่าขีด จำกัด เป็น -1 ทำอะไร
MonkeyBonkey

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "ถ้า numberToReturn เป็น 0, db จะใช้ขนาดผลตอบแทนเริ่มต้นหากตัวเลขเป็นค่าลบฐานข้อมูลจะส่งคืนหมายเลขนั้นและปิดเคอร์เซอร์ "
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และมีความจำเป็นต้องพบเอกสารที่มีจำนวนสุ่มที่ใกล้ที่สุด$lterand

และแน่นอนว่าคุณต้องการทำดัชนีในฟิลด์สุ่ม:

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

หากคุณกำลังสอบถามดัชนีอยู่เพียงแค่วางดัชนีผนวกrandom: 1และเพิ่มอีกครั้ง


7
และนี่คือวิธีง่ายๆในการเพิ่มฟิลด์สุ่มให้กับทุกเอกสารในคอลเลกชัน ฟังก์ชั่น setRandom () {db.topics.find (). forEach (ฟังก์ชั่น (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey

8
นี่เป็นการเลือกเอกสารแบบสุ่ม แต่ถ้าคุณทำมากกว่าหนึ่งครั้งการค้นหาจะไม่ขึ้นกับ คุณมีโอกาสมากขึ้นที่จะได้รับเอกสารฉบับเดียวกันสองครั้งติดต่อกันโดยบังเอิญว่าจะมีการสุ่มเลือก
ขาด

12
ดูเหมือนว่าการใช้งานการบีบอัดแบบวงกลมไม่ดี มันยิ่งแย่กว่าที่คนบอกว่าขาด: แม้แต่การค้นหาเพียงครั้งเดียวก็ลำเอียงเพราะตัวเลขสุ่มไม่ได้กระจายอย่างเท่าเทียมกัน ในการดำเนินการอย่างถูกต้องคุณจะต้องมีชุดของตัวเลขสุ่ม 10 ตัวต่อเอกสาร ยิ่งคุณใช้หมายเลขสุ่มต่อเอกสารมากเท่าไหร่การกระจายสัญญาณจะยิ่งเท่ากัน
โทมัส

4
บัตร MongoDB JIRA ยังมีชีวิตอยู่: jira.mongodb.org/browse/SERVER-533 ไปแสดงความคิดเห็นและลงคะแนนหากคุณต้องการคุณสมบัติ
David J.

1
จดประเภทของข้อแม้ที่กล่าวถึง สิ่งนี้ไม่ทำงานอย่างมีประสิทธิภาพกับเอกสารจำนวนเล็กน้อย ให้สองรายการด้วยรหัสสุ่ม 3 และ 63 เอกสาร # 63 จะถูกเลือกบ่อย$gteครั้งมากขึ้น stackoverflow.com/a/9499484/79201โซลูชันทางเลือกจะทำงานได้ดีขึ้นในกรณีนี้
Ryan Schumacher

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 )

สิ่งนี้ต้องการเพียงหนึ่งเคียวรีและไม่มีการตรวจสอบ null รวมถึงโค้ดนั้นสะอาดเรียบง่ายและยืดหยุ่น คุณสามารถใช้แกน Y ของ geopoint เพื่อเพิ่มมิติการสุ่มที่สองในการสืบค้นของคุณ


8
ฉันชอบคำตอบนี้มันมีประสิทธิภาพมากที่สุดที่ฉันเคยเห็นซึ่งไม่จำเป็นต้องยุ่งเกี่ยวกับฝั่งเซิร์ฟเวอร์
Tony Million

4
นอกจากนี้ยังมีอคติต่อเอกสารที่มีจุดไม่กี่จุดในบริเวณใกล้เคียง
โทมัส

6
นั่นเป็นความจริงและมีปัญหาอื่น ๆ เช่นกัน: เอกสารมีความสัมพันธ์อย่างมากกับปุ่มสุ่มดังนั้นจึงคาดเดาได้สูงว่าเอกสารใดที่จะถูกส่งกลับเป็นกลุ่มหากคุณเลือกเอกสารหลายชุด นอกจากนี้เอกสารที่อยู่ใกล้กับขอบเขต (0 และ 1) มีโอกาสน้อยที่จะถูกเลือก หลังสามารถแก้ไขได้โดยใช้ geomapping ทรงกลมซึ่งล้อมรอบที่ขอบ อย่างไรก็ตามคุณควรเห็นคำตอบนี้เป็นสูตรตำราอาหารที่ปรับปรุงใหม่ไม่ใช่กลไกการเลือกแบบสุ่มที่สมบูรณ์แบบ มันสุ่มพอสำหรับวัตถุประสงค์ส่วนใหญ่
Nico de Poel

@ NicodePoel ฉันชอบคำตอบของคุณเช่นเดียวกับความคิดเห็นของคุณ! และฉันมีคำถามสองสามข้อสำหรับคุณ: 1- คุณจะรู้ได้อย่างไรว่าจุดที่อยู่ใกล้กับขอบเขต 0 และ 1 นั้นมีโอกาสน้อยที่จะถูกเลือกขึ้นอยู่กับพื้นที่ทางคณิตศาสตร์บางส่วนหรือไม่? 2 - คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับ การเลือกแบบสุ่มจะดีขึ้นอย่างไรและจะทำอย่างไรใน MongoDB ... ชื่นชม!
securecurve

ประเมินความคิดของคุณ ในที่สุดฉันมีรหัสที่ดีที่เป็นมิตรกับ CPU และ RAM มาก! ขอบคุณ
Qais Bsharat

21

สูตรต่อไปนี้ช้ากว่าโซลูชันตำราทำอาหาร 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% แต่ดีมาก (และสามารถปรับปรุงได้ถ้าจำเป็น)

สูตรนี้ไม่สมบูรณ์ - โซลูชั่นที่สมบูรณ์แบบจะมีคุณสมบัติในตัวตามที่คนอื่น ๆ ได้กล่าวไว้
อย่างไรก็ตามมันควรเป็นการประนีประนอมที่ดีสำหรับวัตถุประสงค์หลายประการ


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

นั่นเป็นตรรกะทั่วไปในการแทนเชลล์และปรับได้ง่าย

ดังนั้นในจุด:

  • ค้นหาค่าคีย์หลักขั้นต่ำและสูงสุดในคอลเลกชัน

  • สร้างตัวเลขสุ่มที่อยู่ระหว่างการประทับเวลาของเอกสารเหล่านั้น

  • เพิ่มหมายเลขสุ่มลงในค่าต่ำสุดและค้นหาเอกสารแรกที่มากกว่าหรือเท่ากับค่านั้น

สิ่งนี้ใช้ "การเติมเต็ม" จากค่าการประทับเวลาใน "hex" เพื่อสร้างObjectIdค่าที่ถูกต้องเนื่องจากเป็นสิ่งที่เรากำลังมองหา การใช้จำนวนเต็มเป็น_idค่านั้นง่ายกว่า แต่เป็นแนวคิดพื้นฐานเดียวกันในประเด็น


ฉันมีคอลเลกชันจำนวน 300,000 000 บรรทัด นี่เป็นทางออกเดียวที่ใช้ได้และเร็วพอ
Nikos

8

ใน Python ใช้ pymongo:

import random

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

5
น่าสังเกตว่าภายในนี้จะใช้การข้ามและ จำกัด เช่นเดียวกับคำตอบอื่น ๆ
JohnnyHK

คำตอบของคุณถูกต้อง อย่างไรก็ตามโปรดแทนที่count()ด้วยestimated_document_count()ตามที่count()เลิกใช้ใน Mongdo v4.2
3848207

8

ตอนนี้คุณสามารถใช้การรวม ตัวอย่าง:

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

เห็นเอกสาร


3
หมายเหตุ: $ ตัวอย่างอาจได้รับเอกสารเดียวกันมากกว่าหนึ่งครั้ง
Saman Shafigh

6

มันยากถ้าไม่มีข้อมูลที่จะปิด ฟิลด์ _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);

1
ความคิดเห็นใดที่มีลักษณะเป็นอย่างไรใน PHP หรืออย่างน้อยคุณใช้ภาษาอะไรข้างต้น หลามมันคืออะไร?
Marcin

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

มันจะเป็นไปได้อย่างง่ายดายที่จะเอียงวันที่สุ่มเพื่อพิจารณาการเติบโตของฐานข้อมูลระดับสูง
Martin Nowak

นี่เป็นวิธีที่ดีที่สุดสำหรับคอลเลกชันขนาดใหญ่มากก็ทำงานที่ O (1) unline ข้าม () หรือ 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;
}

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

ฟังก์ชั่นลดการทำงานดังกล่าวข้างต้นเพราะเพียงหนึ่งปุ่ม ('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) คือจำนวนเอกสารที่จะดึง

วิธีการนี้อาจทำให้เกิดปัญหากับฐานข้อมูลที่แตกออก


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

ที่นี่คุณไม่จำเป็นต้องใช้พื้นที่ในการจัดเก็บตัวเลขสุ่มในคอลเลกชัน


1

ฉันขอแนะนำให้เพิ่มฟิลด์ int แบบสุ่มให้กับแต่ละวัตถุ จากนั้นคุณสามารถทำ

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

เพื่อเลือกเอกสารแบบสุ่ม ตรวจสอบให้แน่ใจว่าคุณมั่นใจในดัชนี ({random_field: 1})


2
หากระเบียนแรกในคอลเลกชันของคุณมีค่า random_field ค่อนข้างสูงจะไม่ถูกส่งคืนเกือบตลอดเวลาหรือไม่
thehiatus

2
thehaitus ถูกต้องมันจะ - ไม่เหมาะสำหรับวัตถุประสงค์ใด ๆ
Heptic

7
วิธีการแก้ปัญหานี้ผิดอย่างสมบูรณ์การเพิ่มตัวเลขแบบสุ่ม (ลองนึกภาพในระหว่าง 0 a 2 ^ 32-1) ไม่รับประกันการกระจายที่ดีและการใช้ $ gte ทำให้แย่ที่สุดเนื่องจากการเลือกแบบสุ่มของคุณจะไม่ใกล้เคียง ไปยังหมายเลขสุ่มหลอก ฉันไม่แนะนำให้ใช้แนวคิดนี้เลย
Maximiliano Rios

1

เมื่อฉันเผชิญกับวิธีแก้ปัญหาที่คล้ายกันฉันย้อนรอยและพบว่าคำขอทางธุรกิจนั้นจริง ๆ แล้วสำหรับการสร้างรูปแบบการหมุนของสินค้าคงคลังที่นำเสนอ ในกรณีนี้มีตัวเลือกที่ดีกว่ามากซึ่งมีคำตอบจากเครื่องมือค้นหาอย่าง Solr ไม่ใช่ที่เก็บข้อมูลอย่าง MongoDB

กล่าวโดยย่อเกี่ยวกับความต้องการในการ "หมุนเวียนเนื้อหาอย่างชาญฉลาด" สิ่งที่เราควรทำแทนการใช้หมายเลขสุ่มในเอกสารทั้งหมดคือการรวมเครื่องมือปรับคะแนน q ส่วนตัว ในการดำเนินการนี้ด้วยตนเองโดยสมมติว่ามีผู้ใช้จำนวนน้อยคุณสามารถจัดเก็บเอกสารต่อผู้ใช้ที่มีรหัสผลิตภัณฑ์จำนวนการแสดงผลจำนวนการคลิกผ่านวันที่เห็นล่าสุดและปัจจัยอื่น ๆ ที่ธุรกิจพบว่ามีความหมายในการคำนวณคะแนน aq ส่วนขยาย เมื่อดึงชุดที่จะแสดงโดยทั่วไปแล้วคุณร้องขอเอกสารเพิ่มเติมจากแหล่งข้อมูลมากกว่าที่ผู้ใช้ร้องขอจากนั้นใช้ตัวปรับคะแนน q ใช้จำนวนระเบียนที่ร้องขอโดยผู้ใช้ปลายทางจากนั้นสุ่มหน้าผลลัพธ์เล็ก ๆ ตั้งค่าดังนั้นเพียงเรียงลำดับเอกสารในเลเยอร์แอปพลิเคชัน (ในหน่วยความจำ)

หากจักรวาลของผู้ใช้มีขนาดใหญ่เกินไปคุณสามารถจัดหมวดหมู่ผู้ใช้เป็นกลุ่มพฤติกรรมและดัชนีโดยกลุ่มพฤติกรรมแทนที่จะเป็นผู้ใช้

หากจักรวาลของผลิตภัณฑ์มีขนาดเล็กเพียงพอคุณสามารถสร้างดัชนีต่อผู้ใช้

ฉันพบว่าเทคนิคนี้มีประสิทธิภาพมากขึ้น แต่ที่สำคัญกว่านั้นมีประสิทธิภาพมากกว่าในการสร้างประสบการณ์ที่เกี่ยวข้องและคุ้มค่าในการใช้โซลูชันซอฟต์แวร์


1

ไม่ใช่วิธีแก้ปัญหาทำงานได้ดีสำหรับฉัน โดยเฉพาะอย่างยิ่งเมื่อมีช่องว่างจำนวนมากและชุดเล็ก นี้ทำงานได้ดีมากสำหรับฉัน (ใน php):

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

คุณระบุภาษา แต่ไม่ใช่ห้องสมุดที่คุณใช้ใช่ไหม
เบนจามิ

FYI มีสภาพการแข่งขันที่นี่หากเอกสารถูกลบระหว่างบรรทัดแรกและบรรทัดที่สาม find+ ยังskipไม่ดีคุณกำลังส่งคืนเอกสารทั้งหมดเพื่อเลือก: S
Martin Konecny


1

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

0

หากคุณมีรหัสประจำตัวที่เรียบง่ายคุณสามารถเก็บรหัสทั้งหมดไว้ในอาร์เรย์จากนั้นเลือกรหัสสุ่ม (คำตอบทับทิม):

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

0

การใช้แผนที่ / ลดขนาดคุณสามารถได้รับการบันทึกแบบสุ่มไม่จำเป็นต้องมีประสิทธิภาพมากนักทั้งนี้ขึ้นอยู่กับขนาดของคอลเลกชันที่กรองแล้วซึ่งคุณได้ทำงานด้วย

ฉันได้ทดสอบวิธีการนี้กับเอกสาร 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

หากการเลือกแบบ "สุ่ม" นี้ถูกสร้างขึ้นในการค้นหาดัชนีแทนที่จะรวบรวมรหัสลงในอาร์เรย์แล้วเลือกหนึ่งรายการสิ่งนี้จะช่วยได้อย่างไม่น่าเชื่อ (ไปโหวตเลย!)


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

PS วิธีค้นหาเรคคอร์ดแบบสุ่มในคำถามmongodbถูกทำเครื่องหมายว่าซ้ำกับคำถามนี้ ความแตกต่างคือว่าคำถามนี้ถามอย่างชัดเจนเกี่ยวกับการบันทึกเดียวเป็นอีกหนึ่งอย่างชัดเจนเกี่ยวกับการรับเอกสารสุ่มs


-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

ตกลงอาจไม่มีประสิทธิภาพ แต่ก็สะดวก ลองนี้ถ้าขนาดข้อมูลของคุณไม่ใหญ่เกินไป
Zack Xu

3
แน่นอน แต่คำถามเดิมนั้นมีไว้สำหรับคอลเลกชันที่มีเอกสาร 100 ล้านฉบับดังนั้นนี่จะเป็นทางออกที่เลวร้ายสำหรับกรณีนี้!
JohnnyHK

-2

คุณสามารถใช้อาเรย์สับเปลี่ยนหลังจากเรียกใช้คิวรีของคุณ

var shuffle = ต้องการ ('shuffle-array');

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


-7

สิ่งนี้ทำงานได้อย่างมีประสิทธิภาพและเชื่อถือได้คือ:

เพิ่มเขตข้อมูลที่เรียกว่า "สุ่ม" ให้กับแต่ละเอกสารและกำหนดค่าสุ่มให้กับเพิ่มดัชนีสำหรับเขตข้อมูลสุ่มและดำเนินการดังนี้:

สมมติว่าเรามีคอลเลกชันของเว็บลิงค์ที่เรียกว่า "ลิงค์" และเราต้องการลิงค์สุ่มจากมัน:

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

เพื่อให้แน่ใจว่าลิงก์เดิมจะไม่ปรากฏขึ้นเป็นครั้งที่สองให้อัปเดตฟิลด์สุ่มด้วยหมายเลขสุ่มใหม่:

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

2
เหตุใดจึงต้องอัปเดตฐานข้อมูลเมื่อคุณสามารถเลือกคีย์สุ่มแบบอื่นได้
Jason S

คุณอาจไม่มีรายการของปุ่มเพื่อเลือกแบบสุ่ม
ไมค์

ดังนั้นคุณต้องเรียงลำดับการสะสมทั้งหมดในแต่ละครั้งหรือไม่ และสิ่งที่เกี่ยวกับบันทึกโชคร้ายที่มีตัวเลขสุ่มขนาดใหญ่? พวกเขาจะไม่ถูกเลือก
Fantius

1
คุณต้องทำเช่นนี้เพราะโซลูชันอื่น ๆ โดยเฉพาะอย่างยิ่งที่แนะนำในหนังสือ MongoDB ไม่ทำงาน หากการค้นหาครั้งแรกล้มเหลวการค้นหาครั้งที่สองจะส่งคืนรายการด้วยค่าสุ่มที่เล็กที่สุดเสมอ หากคุณทำดัชนีแบบสุ่มมากไปน้อยแบบสอบถามแรกจะส่งคืนรายการที่มีหมายเลขสุ่มมากที่สุดเสมอ
trainwreck

การเพิ่มฟิลด์ในแต่ละเอกสาร? ฉันคิดว่ามันไม่แนะนำให้เลือก
CS_noob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.