Bản ghi ngẫu nhiên từ MongoDB


336

Tôi đang tìm cách để có được một kỷ lục ngẫu nhiên từ một kỷ lục lớn (100 triệu) mongodb.

Cách nhanh nhất và hiệu quả nhất để làm như vậy là gì? Dữ liệu đã có sẵn và không có trường nào tôi có thể tạo một số ngẫu nhiên và có được một hàng ngẫu nhiên.

Bất kỳ đề xuất?


2
Xem thêm câu hỏi SO này có tiêu đề "Đặt một kết quả được đặt ngẫu nhiên trong mongo" . Suy nghĩ về việc sắp xếp ngẫu nhiên một tập kết quả là một phiên bản tổng quát hơn của câu hỏi này - mạnh mẽ hơn và hữu ích hơn.
David J.

11
Câu hỏi này tiếp tục xuất hiện. Thông tin mới nhất có thể được tìm thấy tại yêu cầu tính năng để nhận các mục ngẫu nhiên từ bộ sưu tập trong trình theo dõi vé MongoDB. Nếu được thực hiện nguyên bản, nó có thể sẽ là lựa chọn hiệu quả nhất. (Nếu bạn muốn tính năng này, hãy bỏ phiếu cho nó.)
David J.

Đây có phải là một bộ sưu tập sharded?
Dylan Tong

3
Câu trả lời đúng đã được @JohnnyHK đưa ra dưới đây: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian

Có ai biết điều này chậm hơn bao nhiêu so với việc chỉ thu âm đầu tiên không? Tôi đang tranh luận liệu có đáng để lấy một mẫu ngẫu nhiên để làm một cái gì đó hay không chỉ làm theo thứ tự.
David Kong

Câu trả lời:


247

Bắt đầu với phiên bản 3.2 của MongoDB, bạn có thể nhận được N tài liệu ngẫu nhiên từ một bộ sưu tập bằng cách sử dụng $sampletoán tử đường ống tổng hợp:

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

Nếu bạn muốn chọn (các) tài liệu ngẫu nhiên từ một tập hợp con được lọc của bộ sưu tập, hãy thêm một $matchgiai đoạn vào đường ống:

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

Như đã lưu ý trong các ý kiến, khi sizelớn hơn 1, có thể có các bản sao trong mẫu tài liệu được trả về.


12
Đây là một cách tốt, nhưng hãy nhớ rằng nó KHÔNG đảm bảo rằng không có bản sao của cùng một đối tượng trong mẫu.
Matheus Araujo

10
@MeditusAraujo sẽ không thành vấn đề nếu bạn muốn có một bản ghi nhưng dù sao cũng là điểm tốt
Toby

3
Không phải là mô phạm nhưng câu hỏi không chỉ định phiên bản MongoDB, vì vậy tôi cho rằng có phiên bản mới nhất là hợp lý.
dalanmiller

2
@Nepoxx Xem các tài liệu liên quan đến việc xử lý có liên quan.
JohnnyHK

2
@brycejl Điều đó sẽ có một lỗi nghiêm trọng là không khớp bất cứ thứ gì nếu giai đoạn mẫu $ không chọn bất kỳ tài liệu phù hợp nào.
JohnnyHK

115

Thực hiện đếm tất cả các bản ghi, tạo một số ngẫu nhiên trong khoảng từ 0 đến số đếm và sau đó thực hiện:

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

139
Thật không may bỏ qua () là không hiệu quả vì nó phải quét nhiều tài liệu. Ngoài ra, có một điều kiện cuộc đua nếu các hàng được loại bỏ giữa việc đếm và chạy truy vấn.
mstearn

6
Lưu ý rằng số ngẫu nhiên phải nằm trong khoảng từ 0 đến số đếm (không bao gồm). Tức là, nếu bạn có 10 mục, số ngẫu nhiên phải nằm trong khoảng từ 0 đến 9. Nếu không, con trỏ có thể cố gắng bỏ qua mục cuối cùng và không có gì được trả về.
matt

4
Cảm ơn, làm việc hoàn hảo cho mục đích của tôi. @mstearn, nhận xét của bạn về cả điều kiện hiệu quả và chủng tộc là hợp lệ, nhưng đối với các bộ sưu tập không có vấn đề gì (trích xuất lô máy chủ một lần trong bộ sưu tập mà hồ sơ không bị xóa), điều này vượt trội hơn rất nhiều so với hacky (IMO) giải pháp trong Mongo Cookbook.
Michael Moussa

4
việc đặt giới hạn thành -1 sẽ làm gì?
MonkeyBonkey

@MonkeyBonkey tài liệu "
ceejayoz

86

Cập nhật cho MongoDB 3.2

3.2 đã giới thiệu $ mẫu cho đường ống tổng hợp.

Ngoài ra còn có một bài viết blog tốt về việc đưa nó vào thực tế.

Đối với phiên bản cũ hơn (câu trả lời trước)

Đây thực sự là một yêu cầu tính năng: http://jira.mongodb.org/browse/SERVER-533 nhưng nó đã được gửi trong mục "Không sửa chữa."

Sách dạy nấu ăn có một công thức rất hay để chọn một tài liệu ngẫu nhiên trong bộ sưu tập: http://cookbook.mongodb.org/potypes/random-attribution/

Để diễn giải công thức, bạn chỉ định số ngẫu nhiên cho tài liệu của mình:

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

Sau đó chọn một tài liệu ngẫu nhiên:

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

Truy vấn bằng cả hai $gte$ltelà cần thiết để tìm tài liệu với một số ngẫu nhiên gần nhất rand.

Và tất nhiên bạn sẽ muốn lập chỉ mục trên trường ngẫu nhiên:

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

Nếu bạn đã truy vấn theo một chỉ mục, chỉ cần thả nó, nối random: 1vào nó và thêm lại nó.


7
Và đây là một cách đơn giản để thêm trường ngẫu nhiên vào mọi tài liệu trong bộ sưu tập. hàm setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey

8
Điều này chọn một tài liệu ngẫu nhiên, nhưng nếu bạn làm nhiều lần, việc tra cứu không độc lập. Bạn có nhiều khả năng nhận được cùng một tài liệu hai lần liên tiếp so với cơ hội ngẫu nhiên sẽ ra lệnh.
thiếu

12
Trông giống như một thực hiện xấu của băm tròn. Nó thậm chí còn tệ hơn cả người thiếu nói: thậm chí một lần tra cứu bị sai lệch vì các số ngẫu nhiên không được phân bổ đều. Để làm điều này đúng cách, bạn cần một bộ gồm 10 số ngẫu nhiên cho mỗi tài liệu. Bạn càng sử dụng nhiều số ngẫu nhiên trên mỗi tài liệu, phân phối đầu ra càng đồng đều.
Thomas

4
Vé MongoDB JIRA vẫn còn sống: jira.mongodb.org/browse/SERVER-533 Hãy bình luận và bỏ phiếu nếu bạn muốn tính năng này.
David J.

1
Hãy lưu ý các loại cảnh báo được đề cập. Điều này không hoạt động hiệu quả với số lượng nhỏ tài liệu. Đưa ra hai mục có khóa ngẫu nhiên là 3 và 63. Tài liệu # 63 sẽ được chọn thường xuyên hơn ở vị trí $gteđầu tiên. Giải pháp thay thế stackoverflow.com/a/9499484/79201 sẽ hoạt động tốt hơn trong trường hợp này.
Ryan Schumacher

56

Bạn cũng có thể sử dụng tính năng lập chỉ mục không gian địa lý của MongoDB để chọn các tài liệu 'gần nhất' với một số ngẫu nhiên.

Đầu tiên, cho phép lập chỉ mục không gian địa lý trên một bộ sưu tập:

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

Để tạo một loạt các tài liệu với các điểm ngẫu nhiên trên trục X:

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

Sau đó, bạn có thể nhận được một tài liệu ngẫu nhiên từ bộ sưu tập như thế này:

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

Hoặc bạn có thể truy xuất một số tài liệu gần nhất với một điểm ngẫu nhiên:

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

Điều này chỉ yêu cầu một truy vấn và không có kiểm tra null, cộng với mã sạch, đơn giản và linh hoạt. Bạn thậm chí có thể sử dụng trục Y của công cụ địa lý để thêm thứ nguyên ngẫu nhiên thứ hai vào truy vấn của mình.


8
Tôi thích câu trả lời này, đây là câu trả lời hiệu quả nhất mà tôi thấy không yêu cầu nhiều thứ lộn xộn về phía máy chủ.
Tony Triệu

4
Điều này cũng thiên về các tài liệu xảy ra có một vài điểm trong vùng lân cận của họ.
Thomas

6
Điều đó cũng đúng và cũng có một số vấn đề khác: các tài liệu có mối tương quan chặt chẽ với các khóa ngẫu nhiên của chúng, do đó, rất có thể dự đoán tài liệu nào sẽ được trả lại thành một nhóm nếu bạn chọn nhiều tài liệu. Ngoài ra, các tài liệu gần với giới hạn (0 và 1) ít có khả năng được chọn. Điều thứ hai có thể được giải quyết bằng cách sử dụng geomaps hình cầu, bao quanh các cạnh. Tuy nhiên, bạn nên xem câu trả lời này là một phiên bản cải tiến của công thức nấu ăn, không phải là một cơ chế lựa chọn ngẫu nhiên hoàn hảo. Nó đủ ngẫu nhiên cho hầu hết các mục đích.
Nico de Poel

@NicodePoel, tôi thích câu trả lời của bạn cũng như nhận xét của bạn! Và tôi có một vài câu hỏi cho bạn: 1- Làm thế nào để bạn biết rằng các điểm gần với giới hạn 0 và 1 ít có khả năng được chọn, đó có phải dựa trên một số nền tảng toán học không?, 2- Bạn có thể giải thích nhiều hơn về hình học hình cầu, Làm thế nào nó sẽ tốt hơn cho lựa chọn ngẫu nhiên và làm thế nào để làm điều đó trong MongoDB? ... Đánh giá cao!
bảo đảm

Làm phong phú ý tưởng của bạn. Cuối cùng, tôi có một mã tuyệt vời thân thiện với CPU & RAM! Cảm ơn bạn
Qais Bsharat

21

Công thức sau đây chậm hơn một chút so với giải pháp sách dạy nấu ăn (thêm một khóa ngẫu nhiên trên mỗi tài liệu), nhưng trả về các tài liệu ngẫu nhiên được phân phối đều hơn. Đó là một ít phân phối đồng đều hơn so với skip( random )giải pháp, nhưng nhanh hơn và không an toàn hơn trong trường hợp tài liệu bị xóa.

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

Nó cũng yêu cầu bạn thêm trường "ngẫu nhiên" ngẫu nhiên vào tài liệu của mình để không quên thêm trường này khi bạn tạo chúng: bạn có thể cần phải khởi tạo bộ sưu tập của mình như được hiển thị bởi Geoffrey

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

Kết quả điểm chuẩn

Phương pháp này nhanh hơn nhiều so với skip()phương pháp (của ceejayoz) và tạo ra các tài liệu ngẫu nhiên đồng đều hơn phương pháp "sách dạy nấu ăn" được báo cáo bởi Michael:

Đối với bộ sưu tập có 1.000.000 phần tử:

  • Phương pháp này mất ít hơn một phần nghìn giây trên máy của tôi

  • các skip()phương pháp có 180 ms trung bình

Phương pháp sách dạy nấu ăn sẽ khiến số lượng lớn tài liệu không bao giờ được chọn vì số ngẫu nhiên không ủng hộ chúng.

  • Phương pháp này sẽ chọn tất cả các yếu tố đồng đều theo thời gian.

  • Trong điểm chuẩn của tôi, nó chỉ chậm hơn 30% so với phương pháp nấu ăn.

  • sự ngẫu nhiên không hoàn hảo 100% nhưng nó rất tốt (và nó có thể được cải thiện nếu cần thiết)

Công thức này không hoàn hảo - giải pháp hoàn hảo sẽ là một tính năng tích hợp như những người khác đã lưu ý.
Tuy nhiên, nó nên là một sự thỏa hiệp tốt cho nhiều mục đích.


10

Đây là một cách sử dụng các ObjectIdgiá trị mặc định cho _idvà một chút toán học và logic.

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

Đó là logic chung trong biểu diễn vỏ và dễ dàng thích nghi.

Vì vậy, trong các điểm:

  • Tìm các giá trị khóa chính tối thiểu và tối đa trong bộ sưu tập

  • Tạo một số ngẫu nhiên nằm giữa dấu thời gian của các tài liệu đó.

  • Thêm số ngẫu nhiên vào giá trị tối thiểu và tìm tài liệu đầu tiên lớn hơn hoặc bằng giá trị đó.

Điều này sử dụng "phần đệm" từ giá trị dấu thời gian trong "hex" để tạo thành một giá ObjectIdtrị hợp lệ vì đó là những gì chúng tôi đang tìm kiếm. Sử dụng số nguyên làm _idgiá trị về cơ bản là đơn giản hơn nhưng cùng một ý tưởng cơ bản trong các điểm.


Tôi có một bộ sưu tập 300 000 000 dòng. Đây là giải pháp duy nhất hoạt động và nó đủ nhanh.
Nikos

8

Trong Python sử dụng pymongo:

import random

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

5
Đáng lưu ý rằng trong nội bộ, điều này sẽ sử dụng bỏ qua và giới hạn, giống như nhiều câu trả lời khác.
JohnnyHK

Câu trả lời của bạn là đúng. Tuy nhiên, vui lòng thay thế count()bằng estimated_document_count()như count()không được dùng trong Mongdo v4.2.
dùng3848207

8

Bây giờ bạn có thể sử dụng tổng hợp. Thí dụ:

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

Xem tài liệu .


3
Lưu ý: $ sample có thể nhận được cùng một tài liệu nhiều lần
Saman Shafigh

6

thật khó khăn nếu không có dữ liệu để khóa. lĩnh vực _id là gì? Có phải họ mongodb id id? Nếu vậy, bạn có thể nhận được các giá trị cao nhất và thấp nhất:

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

sau đó nếu bạn giả sử id được phân phối đồng đều (nhưng chúng không, nhưng ít nhất đó là một sự khởi đầu):

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
Bất kỳ ý tưởng nào sẽ trông như thế nào trong PHP? hoặc ít nhất bạn đã sử dụng ngôn ngữ nào ở trên? Có phải là Python không?
Marcin

6

Sử dụng Python (pymongo), hàm tổng hợp cũng hoạt động.

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

Cách tiếp cận này nhanh hơn rất nhiều so với việc chạy truy vấn cho một số ngẫu nhiên (ví dụ: Collection.find ([Random_int]). Đây là trường hợp đặc biệt cho các bộ sưu tập lớn.


5

Bạn có thể chọn dấu thời gian ngẫu nhiên và tìm kiếm đối tượng đầu tiên được tạo sau đó. Nó sẽ chỉ quét một tài liệu duy nhất, mặc dù nó không nhất thiết phải cung cấp cho bạn một bản phân phối thống nhất.

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

Có thể dễ dàng có thể sai lệch ngày ngẫu nhiên để tính đến sự tăng trưởng cơ sở dữ liệu siêu tuyến.
Martin Nowak

đây là phương pháp tốt nhất cho các bộ sưu tập rất lớn, nó hoạt động ở O (1), bỏ qua bỏ qua () hoặc đếm () được sử dụng trong các giải pháp khác ở đây
marmor 2/11/2016

4

Giải pháp của tôi trên 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

Để có được số lượng tài liệu ngẫu nhiên xác định mà không trùng lặp:

  1. đầu tiên nhận được tất cả các id
  2. lấy kích thước của tài liệu
  3. lặp lấy chỉ số ngẫu nhiên và bỏ qua trùng lặp

    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

Tôi sẽ đề nghị sử dụng map / less, trong đó bạn sử dụng chức năng bản đồ để chỉ phát ra khi giá trị ngẫu nhiên vượt quá xác suất cho trước.

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

Hàm lessef ở trên hoạt động vì chỉ có một phím ('1') được phát ra từ chức năng bản đồ.

Giá trị của "xác suất" được xác định trong "phạm vi", khi gọi mapRreduce (...)

Sử dụng mapReduce như thế này cũng có thể sử dụng được trên một db bị phân mảnh.

Nếu bạn muốn chọn chính xác n tài liệu m từ db, bạn có thể làm như thế này:

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

Trong đó "CountTotal" (m) là số lượng tài liệu trong db và "CountSubset" (n) là số lượng tài liệu cần truy xuất.

Cách tiếp cận này có thể đưa ra một số vấn đề trên cơ sở dữ liệu bị phân mảnh.


4
Thực hiện quét toàn bộ bộ sưu tập để trả về 1 phần tử ... đây phải là kỹ thuật kém hiệu quả nhất để thực hiện.
Thomas

1
Bí quyết là, đó là một giải pháp chung để trả về một số phần tử ngẫu nhiên tùy ý - trong trường hợp đó sẽ nhanh hơn các giải pháp khác khi nhận> 2 phần tử ngẫu nhiên.
torbenl

2

Bạn có thể chọn ngẫu nhiên _id và trả về đối tượng tương ứng:

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

Ở đây bạn không cần phải dành không gian cho việc lưu trữ số ngẫu nhiên trong bộ sưu tập.


1

Tôi khuyên bạn nên thêm một trường int ngẫu nhiên cho mỗi đối tượng. Sau đó, bạn có thể chỉ cần làm một

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

để chọn một tài liệu ngẫu nhiên. Chỉ cần đảm bảo rằng bạn đảm bảo Index ({Random_field: 1})


2
Nếu bản ghi đầu tiên trong bộ sưu tập của bạn có giá trị Random_field tương đối cao, nó sẽ không được trả lại gần như mọi lúc chứ?
thehiatus

2
thehaitus là chính xác, nó sẽ - nó không phù hợp cho bất kỳ mục đích nào
Heptic

7
Giải pháp này hoàn toàn sai, việc thêm một số ngẫu nhiên (hãy tưởng tượng trong khoảng từ 0 a 2 ^ 32-1) không đảm bảo bất kỳ phân phối tốt nào và việc sử dụng $ gte làm cho nó thậm chí còn tệ nhất, do lựa chọn ngẫu nhiên của bạn sẽ không gần đến một số giả ngẫu nhiên. Tôi đề nghị không sử dụng khái niệm này bao giờ.
Maximiliano Rios

1

Khi tôi phải đối mặt với một giải pháp tương tự, tôi đã quay lại và thấy rằng yêu cầu kinh doanh thực sự là để tạo ra một số hình thức xoay vòng của hàng tồn kho được trình bày. Trong trường hợp đó, có nhiều tùy chọn tốt hơn, có câu trả lời từ các công cụ tìm kiếm như Solr, không phải các kho lưu trữ dữ liệu như MongoDB.

Nói tóm lại, với yêu cầu "xoay thông minh" nội dung, những gì chúng ta nên làm thay vì một số ngẫu nhiên trên tất cả các tài liệu là bao gồm một công cụ sửa đổi điểm q cá nhân. Để tự thực hiện điều này, giả sử một lượng nhỏ người dùng, bạn có thể lưu trữ tài liệu cho mỗi người dùng có sản phẩm, số lần hiển thị, số lần nhấp, ngày nhìn thấy lần cuối và bất kỳ yếu tố nào khác mà doanh nghiệp thấy có ý nghĩa để tính điểm aq bổ nghĩa. Khi truy xuất tập hợp để hiển thị, thông thường bạn yêu cầu nhiều tài liệu từ kho lưu trữ dữ liệu hơn yêu cầu của người dùng cuối, sau đó áp dụng công cụ sửa đổi điểm q, lấy số lượng hồ sơ được yêu cầu bởi người dùng cuối, sau đó chọn ngẫu nhiên trang kết quả, một chút thiết lập, vì vậy chỉ cần sắp xếp các tài liệu trong lớp ứng dụng (trong bộ nhớ).

Nếu vũ trụ của người dùng quá lớn, bạn có thể phân loại người dùng thành các nhóm hành vi và lập chỉ mục theo nhóm hành vi thay vì người dùng.

Nếu vũ trụ của các sản phẩm đủ nhỏ, bạn có thể tạo một chỉ mục cho mỗi người dùng.

Tôi đã tìm thấy kỹ thuật này hiệu quả hơn nhiều, nhưng quan trọng hơn là hiệu quả hơn trong việc tạo ra trải nghiệm đáng giá, có liên quan khi sử dụng giải pháp phần mềm.


1

không phải là giải pháp làm việc tốt cho tôi đặc biệt là khi có nhiều khoảng trống và thiết lập nhỏ. điều này làm việc rất tốt cho tôi (bằng php):

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

Bạn chỉ định ngôn ngữ, nhưng không phải thư viện bạn đang sử dụng?
Benjamin

FYI, có một điều kiện cuộc đua ở đây nếu một tài liệu bị xóa giữa dòng thứ nhất và thứ ba. Ngoài ra find+ skiplà khá tệ, bạn đang trả lại tất cả các tài liệu chỉ để chọn một: S.
Martin Konecny


1

Sắp xếp / sắp xếp PHP / MongoDB của tôi theo giải pháp RANDOM. Hy vọng điều này sẽ giúp bất cứ ai.

Lưu ý: Tôi có ID số trong bộ sưu tập MongoDB của tôi đề cập đến bản ghi cơ sở dữ liệu MySQL.

Đầu tiên tôi tạo một mảng với 10 số được tạo ngẫu nhiên

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

Trong tập hợp của tôi, tôi sử dụng toán tử đường ống $ addField kết hợp với $ ArrayElemAt và $ mod (modulus). Toán tử mô đun sẽ cho tôi một số từ 0 - 9 mà sau đó tôi sử dụng để chọn một số từ mảng có các số được tạo ngẫu nhiên.

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

Sau đó, bạn có thể sử dụng Pipeline sắp xếp.

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

0

Nếu bạn có một khóa id đơn giản, bạn có thể lưu trữ tất cả các id trong một mảng và sau đó chọn một id ngẫu nhiên. (Câu trả lời của Ruby):

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

0

Sử dụng Bản đồ / Thu nhỏ, bạn chắc chắn có thể nhận được một bản ghi ngẫu nhiên, không nhất thiết phải rất hiệu quả tùy thuộc vào kích thước của bộ sưu tập được lọc mà bạn kết thúc làm việc.

Tôi đã thử nghiệm phương pháp này với 50.000 tài liệu (bộ lọc giảm xuống còn khoảng 30.000) và nó thực thi trong khoảng 400ms trên Intel i3 với ram 16 GB và ổ cứng 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" }
    }
);

Hàm Map chỉ đơn giản là tạo một mảng id của tất cả các tài liệu khớp với truy vấn. Trong trường hợp của tôi, tôi đã thử nghiệm điều này với khoảng 30.000 trong số 50.000 tài liệu có thể.

Hàm Giảm chỉ đơn giản chọn một số nguyên ngẫu nhiên trong khoảng từ 0 đến số mục (-1) trong mảng và sau đó trả về _id đó từ mảng.

400ms nghe có vẻ là một thời gian dài và thực sự, nếu bạn có năm mươi triệu bản ghi thay vì năm mươi nghìn, điều này có thể làm tăng chi phí đến mức không thể sử dụng được trong các tình huống nhiều người dùng.

Có một vấn đề mở để MongoDB đưa tính năng này vào lõi ... https://jira.mongodb.org/browse/SERVER-533

Nếu lựa chọn "ngẫu nhiên" này được tích hợp vào tra cứu chỉ mục thay vì thu thập id vào một mảng và sau đó chọn một, điều này sẽ giúp ích vô cùng. (đi bỏ phiếu lên!)


0

Điều này hoạt động tốt, nhanh, hoạt động với nhiều tài liệu và không yêu cầu randtrường dân cư , cuối cùng sẽ tự điền:

  1. thêm chỉ mục vào trường .rand trên bộ sưu tập của bạn
  2. sử dụng tìm và làm mới, đại loại như:
// 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. Làm thế nào để tìm bản ghi ngẫu nhiên trong câu hỏi mongodb được đánh dấu là bản sao của câu hỏi này. Sự khác biệt là câu hỏi này hỏi rõ ràng về một bản ghi như một câu hỏi khác rõ ràng về việc lấy tài liệu ngẫu nhiên s .


-2

Nếu bạn đang sử dụng mongoid, trình bao bọc tài liệu-đối tượng, bạn có thể thực hiện các thao tác sau trong Ruby. (Giả sử mô hình của bạn là Người dùng)

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

Trong .irbrc của tôi, tôi có

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

Vì vậy, trong bảng điều khiển rails, tôi có thể làm, ví dụ,

rando User
rando Article

để lấy tài liệu ngẫu nhiên từ bất kỳ bộ sưu tập.


1
Điều này là không hiệu quả khủng khiếp vì nó sẽ đọc toàn bộ bộ sưu tập thành một mảng và sau đó chọn một bản ghi.
JohnnyHK

Ok, có thể không hiệu quả, nhưng chắc chắn thuận tiện. hãy thử điều này nếu kích thước dữ liệu của bạn không quá lớn
Zack Xu

3
Chắc chắn, nhưng câu hỏi ban đầu là về một bộ sưu tập với 100 triệu tài liệu nên đây sẽ là một giải pháp rất tệ cho trường hợp đó!
JohnnyHK

-2

bạn cũng có thể sử dụng shuffle-Array sau khi thực hiện truy vấn của mình

var shuffle = Yêu cầu ('shuffle-mảng');

Tài khoản.find (qry, hàm (err, results_array) {new IndexArr = shuffle (results_array);


-7

Những gì hoạt động hiệu quả và đáng tin cậy là đây:

Thêm một trường gọi là "ngẫu nhiên" cho mỗi tài liệu và gán giá trị ngẫu nhiên cho nó, thêm chỉ mục cho trường ngẫu nhiên và tiến hành như sau:

Giả sử chúng tôi có một bộ sưu tập các liên kết web được gọi là "liên kết" và chúng tôi muốn một liên kết ngẫu nhiên từ nó:

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

Để đảm bảo cùng một liên kết sẽ không bật lên lần thứ hai, hãy cập nhật trường ngẫu nhiên của nó với một số ngẫu nhiên mới:

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

2
Tại sao cập nhật cơ sở dữ liệu khi bạn chỉ có thể chọn một khóa ngẫu nhiên khác?
Jason S

Bạn có thể không có danh sách các phím để chọn ngẫu nhiên.
Mike

Vì vậy, bạn phải sắp xếp toàn bộ bộ sưu tập mỗi lần? Và những gì về các hồ sơ không may mắn có số lượng lớn ngẫu nhiên? Họ sẽ không bao giờ được chọn.
Fantius

1
Bạn phải làm điều này bởi vì các giải pháp khác, đặc biệt là giải pháp được đề xuất trong sách MongoDB, không hoạt động. Nếu lần tìm thứ nhất thất bại, lần tìm thứ hai luôn trả về mục có giá trị ngẫu nhiên nhỏ nhất. Nếu bạn lập chỉ mục ngẫu nhiên giảm dần, truy vấn đầu tiên luôn trả về mục có số ngẫu nhiên lớn nhất.
đắm tàu

Thêm một lĩnh vực trong mỗi tài liệu? Tôi nghĩ rằng nó không nên.
CS_noob
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.