MongoDBからのランダムなレコード


336

私は巨大な(1億レコード)からランダムなレコードを取得しようとしています mongodbます。

そうするための最速かつ最も効率的な方法は何ですか?データはすでにそこにあり、乱数を生成してランダムな行を取得できるフィールドはありません。

助言がありますか?


2
mongoでの結果セットのランダムな順序付け」というタイトルのこのSO質問も参照してください。結果セットのランダムな順序付けについて考えることは、この質問のより一般的なバージョンであり、より強力でより便利です。
David J.

11
この質問は増え続けています。MongoDBチケットトラッカーのコレクションからランダムなアイテムを取得する機能リクエストで最新情報が見つかる可能性があります。ネイティブに実装する場合は、おそらく最も効率的なオプションです。(機能が必要な場合は、投票してください。)
David J.

これは断片化されたコレクションですか?
Dylan Tong

3
正しい答えは、以下の@JohnnyHK
Florian

これが最初の記録を取るよりどれだけ遅いか知っている人はいますか?ランダムなサンプルをとって、それを順番に実行するだけの価値があるかどうかについて、私は議論しています。
David Kong

回答:


247

MongoDBの3.2リリース以降、$sample集約パイプライン演算子を使用して、コレクションからN個のランダムドキュメントを取得できます。

// 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 2016年

10
@MatheusAraujoこれは1つのレコードが必要な場合でも問題ありませんが、とにかく良い点
Toby

3
面倒ではありませんが、質問ではMongoDBのバージョンを指定していません。そのため、最新バージョンを使用するのが妥当だと思います。
dalanmiller

2
@Nepoxx関連する処理に関するドキュメントを参照してください。
JohnnyHK

2
@brycejl $ sampleステージが一致するドキュメントを選択しなかった場合、何も一致しないという致命的な欠陥があります。
JohnnyHK

115

すべてのレコードをカウントし、0からカウントまでの乱数を生成してから、次のようにします。

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

139
残念ながら、skip()は多くのドキュメントをスキャンする必要があるため、かなり非効率的です。また、カウントを取得してからクエリを実行するまでの間に行が削除されると、競合状態が発生します。
mstearn 2010年

6
乱数は0からカウント(排他的)の間でなければならないことに注意してください。つまり、10個のアイテムがある場合、乱数は0から9の間でなければなりません。そうでない場合、カーソルは最後のアイテムをスキップしようとして、何も返されません。
マット

4
ありがとう、私の目的のために完全に働きました。@mstearn、効率と競合状態の両方についてのコメントは有効ですが、どちらも重要でないコレクション(レコードが削除されないコレクション内の1回限りのサーバー側バッチ抽出)の場合、これはハッキー(IMO)よりもはるかに優れています。 Mongo Cookbookのソリューション。
Michael Moussa

4
制限を-1に設定するとどうなりますか?
MonkeyBonkey 2013年

@ MonkeyBonkeydocs.mongodb.org / meta - driver / latest / legacy / 「numberToReturnが0の場合、dbはデフォルトの戻りサイズを使用します。数値が負の場合、データベースはその数値を返し、カーソルを閉じます。 」
ceejayoz 2013年

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でクエリを実行する$lteと、最も近い乱数を持つドキュメントを見つける必要がありますrand

そしてもちろん、ランダムなフィールドにインデックスを付けたいでしょう:

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

すでにインデックスに対してクエリを実行している場合は、単にインデックスを削除して追加random: 1し、再度追加します。


7
そして、これは、コレクション内のすべてのドキュメントにランダムフィールドを追加する簡単な方法です。function setRandom(){db.topics.find()。forEach(function(obj){obj.random = Math.random(); db.topics.save(obj);}); } db.eval(setRandom);
Geoffrey

8
これはランダムにドキュメントを選択しますが、複数回行う場合、検索は独立していません。ランダムなチャンスが指示するよりも、同じドキュメントを2回続けて取得する可能性が高くなります。
1

12
循環ハッシュの実装が悪いようです。乱数が均等に分散されていないため、1つのルックアップでもバイアスがかけられます。これを適切に行うには、たとえば、ドキュメントごとに10個の乱数のセットが必要です。ドキュメントごとに使用する乱数が多いほど、出力分布が均一になります。
トーマス

4
MongoDB JIRAチケットはまだ有効です:jira.mongodb.org/browse/SERVER-533 Goコメントと投票して、機能が必要な場合は投票してください。
David J.

1
言及されている警告のタイプに注意してください。これは、少量のドキュメントでは効率的に機能しません。ランダムキーが3と63の2つのアイテムが与えられます。ドキュメント#63は、$gteが最初の場所でより頻繁に選択されます。この場合、代わりのソリューションであるstackoverflow.com/a/9499484/79201がより適切に機能します。
ライアンシューマッハ

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 )

これにはクエリが1つだけ必要で、nullチェックは必要ありません。さらに、コードはクリーンでシンプルで柔軟です。ジオポイントのY軸を使用して、クエリに2番目のランダム次元を追加することもできます。


8
私はこの答えが好きです。サーバー側で何度もいじる必要がない、私が見た中で最も効率的な答えです。
トニーミリオン

4
これは、たまたま近くにポイントがほとんどないドキュメントに偏っています。
トーマス

6
これは真実であり、他の問題もあります。ドキュメントはランダムキーで強く相関しているため、複数のドキュメントを選択した場合、どのドキュメントがグループとして返​​されるかは非常に予測可能です。また、境界(0と1)に近いドキュメントが選択される可能性は低くなります。後者は、エッジで折り返す球状ジオマッピングを使用することで解決できます。ただし、この答えは完全なランダム選択メカニズムではなく、クックブックレシピの改良版として表示されます。それはほとんどの目的のために十分にランダムです。
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];

これがシェル表現の一般的なロジックであり、簡単に適応できます。

だからポイントで:

  • コレクション内の主キーの最小値と最大値を見つける

  • それらのドキュメントのタイムスタンプの間にある乱数を生成します。

  • 乱数を最小値に追加し、その値以上の最初のドキュメントを見つけます。

これは、「16進数」のタイムスタンプ値からの「パディング」を使用して、有効なObjectId値を形成します。整数を_id値として使用することは基本的には簡単ですが、ポイントの基本的な考え方は同じです。


30万000行のコレクションがあります。これが機能する唯一のソリューションであり、十分に高速です。
ニコス

8

pymongoを使用するPythonでは:

import random

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

5
これは、他の多くの回答と同様に、内部的にスキップと制限を使用することに注意してください。
JohnnyHK 2015年

正解です。ただし、Mongdo v4.2で廃止されたasに置き換えcount()てください。estimated_document_count()count()
user3848207


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;

次に、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ではどのように見えるでしょうか?または、少なくとも上記で使用した言語は何ですか?Pythonですか?
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 2015年

これは非常に大規模なコレクションに最適な方法であり、他のソリューションで使用されているO(1)、unline skip()、または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. 最初にすべてのIDを取得
  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

私はmap / reduceを使用することをお勧めします。ここでは、map関数を使用して、ランダムな値が所定の確率を超えている場合にのみ放出します。

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

上記のreducef関数は、map関数から1つのキー( '1')だけが出力されるため、機能します。

「確率」の値は、mapRreduce(...)を呼び出すときに「スコープ」で定義されます。

このようなmapReduceの使用は、分割されたデータベースでも使用できるはずです。

dbからちょうどn個のドキュメントを選択する場合は、次のように実行できます。

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)はデータベース内のドキュメントの数であり、「countSubset」(n)は取得するドキュメントの数です。

このアプローチでは、シャーディングされたデータベースでいくつかの問題が発生する可能性があります。


4
フルコレクションスキャンを実行して1つの要素を返す...これは、最も効率の悪い手法であるに違いありません。
トーマス

1
トリックは、ランダムな要素の任意の数を返す一般的なソリューションであることです。この場合、ランダムな要素を2つ以上取得すると、他のソリューションよりも高速になります。
torbenl 2014

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

ランダムなドキュメントを選択します。必ず確認してください。ensureIndex({random_field:1})


2
コレクションの最初のレコードのrandom_field値が比較的高い場合、ほとんど常に返されませんか?
thehiatus 2013年

2
thehaitusは正しいです、それはそうです-それはいかなる目的にも適していません
ヘプティック2013

7
この解決策は完全に間違っています。乱数を追加すると(0から2 ^ 32-1の間を想像してみてください)、良い分布が保証されるわけではなく、$ gteを使用するとさらに悪くなります。疑似乱数に。私はこの概念を決して使用しないことをお勧めします。
Maximiliano Rios、

1

私が同様のソリューションに直面したとき、私は逆戻りして、ビジネス要求が実際に提示されている在庫の何らかの形のローテーションを作成することであることがわかりました。その場合、MongoDBのようなデータストアではなく、Solrのような検索エンジンからの回答を持つ、より優れたオプションがあります。

つまり、コンテンツを「インテリジェントにローテーション」する必要があるため、すべてのドキュメントにわたって乱数の代わりに行うべきことは、個人のqスコア修飾子を含めることです。これを自分で実装するには、ユーザーの人口が少ないと想定して、productId、インプレッション数、クリックスルー数、最終閲覧日、およびビジネスがaqスコアを計算するのに意味があると判断したその他の要素を含むドキュメントをユーザーごとに保存できます。修飾子。表示するセットを取得する場合、通常、エンドユーザーが要求したよりも多くのドキュメントをデータストアに要求し、次にqスコア修飾子を適用し、エンドユーザーが要求したレコード数を取得し、結果のページをランダム化します。セットなので、アプリケーション層(メモリ内)でドキュメントを並べ替えるだけです。

ユーザーの範囲が広すぎる場合は、ユーザーを行動グループに分類し、ユーザーではなく行動グループごとにインデックスを作成できます。

製品の範囲が十分に小さい場合は、ユーザーごとにインデックスを作成できます。

この手法の方がはるかに効率的であることがわかりましたが、ソフトウェアソリューションの使用に関連する価値ある経験を生み出す上で、より重要なのはより効果的です。


1

解決策のどれも私にはうまくいきませんでした。特にギャップが多く、セットが小さい場合。これは私(php)で非常にうまくいきました:

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

言語を指定しますが、使用しているライブラリは指定しませんか?
ベンジャミン

参考までに、1行目と3行目の間でドキュメントが削除されると、競合状態になります。また、find+ skipはかなり悪いです。1つの:Sを選択するためだけにすべてのドキュメントを返します。
Martin Konecny、2014


1

PHP / MongoDBのRANDOMソリューションによる並べ替え/順序付け。これが誰にも役立つことを願っています。

注:私のMongoDBコレクションには、MySQLデータベースレコードを参照する数値IDがあります。

まず、ランダムに生成された10個の数値で配列を作成します

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

私の集計では、$ addFieldパイプライン演算子と$ arrayElemAtおよび$ mod(modulus)を組み合わせて使用​​しています。モジュラス演算子により、0〜9の数値が得られます。これを使用して、ランダムに生成された数値を持つ配列から数値を選択します。

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

その後、並べ替えパイプラインを使用できます。

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

0

単純なIDキーがある場合は、すべてのIDを配列に格納してから、ランダムなIDを選択できます。(ルビー回答):

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

0

Map / Reduceを使用すると、ランダムなレコードを確実に取得できますが、結果としてフィルター処理されたコレクションのサイズによっては、必ずしも非常に効率的ではありません。

私はこの方法を50,000のドキュメントでテストしました(フィルターはそれを約30,000に減らします)。16GBの RAMとSATA3 HDDを備えたIntel i3 で約400msで実行されます...

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

Map関数は、クエリに一致するすべてのドキュメントのIDの配列を作成するだけです。私の場合、50,000の可能なドキュメントのうち約30,000でこれをテストしました。

Reduce関数は、0と配列内の項目の数(-1)の間のランダムな整数を選択し、その_idを返します。を配列から。

400msは長い時間のように聞こえますが、実際には、5万ではなく5千万のレコードがあった場合、マルチユーザーの状況で使用できなくなるまでオーバーヘッドが増える可能性があります。

MongoDBには、コアにこの機能を含めるための未解決の問題があります... https://jira.mongodb.org/browse/SERVER-533

この「ランダムな」選択が、IDを配列に収集してから1つを選択するのではなく、インデックス検索に組み込まれた場合、これは信じられないほど役立ちます。(投票してください!)


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の質問でランダムなレコードを見つける方法は、この質問の重複としてマークされています。違いは、この質問は、ランダムなドキュメント得ることについて、明示的に他の一つとして、単一のレコードについて、明示的に要求することであるのを


-2

ドキュメントからオブジェクトへのラッパーであるmongoidを使用している場合は、Rubyで以下を実行できます。(モデルがユーザーであると想定)

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

私の.irbrcには、

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

たとえば、Railsコンソールでは、次のことができます。

rando User
rando Article

任意のコレクションからランダムにドキュメントを取得します。


1
これは、コレクション全体を配列に読み込み、1つのレコードを選択するため、非常に非効率的です。
JohnnyHK 2013

わかりました、おそらく非効率的ですが、確かに便利です。データサイズが大きすぎない場合は、これを試してください
Zack Xu

3
もちろん、元の質問は1億のドキュメントを含むコレクションに対するものでした。そのため、これはその場合には非常に悪い解決策になります。
JohnnyHK 2013

-2

クエリを実行した後にshuffle-arrayを使用することもできます

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

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


-7

効率的かつ確実に機能するのはこれです。

「ランダム」というフィールドを各ドキュメントに追加し、それにランダムな値を割り当て、ランダムフィールドのインデックスを追加して、次のように進めます。

「リンク」と呼ばれるWebリンクのコレクションがあり、そこからランダムなリンクが必要だとします。

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

同じリンクが2度目にポップアップしないようにするには、そのランダムフィールドを新しい乱数で更新します。

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

2
別のランダムキーを選択できるのに、なぜデータベースを更新するのですか?
Jason S

ランダムに選択するキーのリストがない可能性があります。
Mike

毎回コレクション全体をソートする必要がありますか?そして、大きな乱数を取得した不運なレコードはどうですか?それらは決して選択されません。
ファンティウス

1
他のソリューション、特にMongoDBブックで提案されているソリューションが機能しないため、これを行う必要があります。最初の検索が失敗した場合、2番目の検索は常に最小のランダム値を持つアイテムを返します。ランダムに降順でインデックスを作成する場合、最初のクエリは常に最大の乱数を持つアイテムを返します。
2012年

各ドキュメントにフィールドを追加しますか?それはお勧めできません。
CS_noob 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.