15

コレクションからランダムなドキュメントを取得する方法についての質問が何度も寄せられており、このトピックに関する提案がありました。

私が必要とするのは、コレクションからいくつかのランダムなドキュメントを取得することです。さらに悪いことに、それらのドキュメントは特定の基準に一致する必要があります (つまり、フィルター処理されます)。たとえば、各記事に「トピック」フィールドがある記事のコレクションがあります。ユーザーが興味のあるトピックを選択すると、データベースは対応する記事を毎回ランダムな順序で表示する必要があります。

明らかに、前述のハックは役に立ちません。私が望むものを達成する唯一の方法は、IDのみを取得する対応するトピックを照会することです:

var arr = db.articles.find({topic: 3}, {_id:1}).toArray();

次に、受信したドキュメントの数に応じてランダムな一連の番号を生成し、その配列のインデックスとして乱数を使用して配列からドキュメント ID を取得し、最後に mongodb に別のリクエストを実行して、ランダムに選択された ID を持つドキュメントを取得します。

ご覧のとおり、特に最初のクエリで返される記事が多すぎる場合は、全体的に少し遅すぎるようです:)

したがって、インデックス内の位置に基づいてインデックスキーでドキュメントを取得するmongodbコマンドがいくつかあると思います。ポイントは、次のようにカバーされた複合インデックスを作成できることです。

db.articles.ensureIndex({topic: 1, _id:1});

そして今、私のクエリは、インデックス内の正しい _ids の連続行をスキャンするだけで済みます。そして、これらの '_ids' 位置によってコレクションからドキュメントを要求できれば、1 回の要求ですべてを実行できます! 何かのようなもの:

var cursor = db.articles.find({topic:3, $indexKeyPosition: {$in: myRandomSequence}});

そのような機能について知っている人はいますか?

4

3 に答える 3

7

$sample最近では、集計機能を使用できるはずです。

例 (未テスト):

db.articles.aggregate([
    { $match : { topic : 3 } },
    { $sample : { size: 3 } }
])

ただし、同じドキュメントが複数回返される場合があることに注意してください。

于 2016-04-20T15:46:32.550 に答える
4

したがって、インデックス内の位置に基づいてインデックスキーでドキュメントを取得するmongodbコマンドがいくつかあると思います。ポイントは、次のようにカバーされた複合インデックスを作成できることです。

いいえ、MongoDB にはそのような関数はありませんが、結果セットをランダム化できるのは良い考えです。それまでの間、ここに JIRA があります: https://jira.mongodb.org/browse/SERVER-533

インデックスを使用できるようにインデックスの位置から選択する方法がないため、結果として単一のラウンドトリップを使用することはできないため、複数のカーソルを開くしかありません。

現在の解決策は、結果セットに含まれるドキュメントの数によって異なります。

結果セットに少数のドキュメントがある場合、これは簡単skip(rand())に解決できますが、インデックスを効果的に使用していないことにlimit(1)注意する必要があります。skip()limit()

これは、Btree 全体をスキャンするという意味ではありませんskip()

これは、結果セットが大きくなり、結果セットが多数にrand()なると、多くの場合と同様に深刻なパフォーマンスの問題が発生することを意味します。

これを解決する良い方法の 1 つは、次のいずれかを維持することです。

そして、その新しいフィールドを使用して、クエリの残りの部分を次のように「スキップ」します。

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();

0to1アイデアを使用して 7 つのランダムな行を取得します。

このランダムソート機能は、常に変化するデータセットに依存して、ソート内でランダム性を作成するのに役立ちます。もちろん、結果セットが継続的に静的である場合、これは機能しません。

batchSize の使用に関しては、ここでは関係ありません。たとえば、BatchSize を使用してすべての結果を取得するというロジックは、通常、BatchSize の絶対最大サイズが 16MB であるため、完全には意味がありません。これは、ドキュメントが大きい場合、思ったとおりの 1 回の往復が得られない可能性があることを意味します。

これはまた、サーバーがこのすべてのデータを一度に送信することを指示するだけであり、サーバーに配置される作業の量を示すのではなく、ネットワーク上で一度に送信されるデータの量を示します。

したがって、複数のカーソルでこれを行う必要があることを考えると(私が推奨する方法)、実行するだけです:

var arr = db.articles.find({topic: 3, rand: {$gte:rand()}}).sort({rand:1}).limit(1);

数回、または必要に応じて何度でも。これは、カーソルの通常の反復とあまり変わらず、適切なインデックスがあれば非常に高速です。

もう1つの方法がありますが、お勧めしません。_idたとえば、MR を 1 時間に 1 回実行するか、別のコレクションを作成する何かをrand()実行できます。これは、最初に示したクエリを実行できることを意味します。

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();

rand()もちろん、異なるため、実際には7つのランダムレコードを取得します。しかし、これはリアルタイムではなく、大規模なデータセット上のサーバーにとってはあまり良いことではないので、そのようなことはお勧めしません.

編集

もう1つの方法があります。自動インクリメント ID を使用すると、一度に$or7 秒を選択するステートメントを実行できます。rand()ただし、これにより、削除という別の問題が発生します。

行を削除rand()すると、存在しない a にヒットする可能性があるため、行は返されません。サーバー側の削除に対抗するために自動インクリメントIDが維持されないため、これを自分で行う必要があります。これは、簡単なことでもスケーラブルなことでもありません。

この$orステートメントに追加するにlimit()は、句で編集することはできません。つまり、サブ選択タイプ$orを実行して、MongoDB が を$or使用して句ごとに 1 つの結果のみを選択するようにすることで、これを回避することはできません$gte

rand()同じことが と の間0にも当てはまります1。これは、$or句を制限できる場合に機能します。

于 2012-12-17T08:53:32.983 に答える
2

(ページネーションと同様に) クエリに一致するドキュメントの数を数えることができます。次に、skip(random_value) と limit(1) を使用して N 個のクエリを作成します。

db.collection.count({field:value,field2:value2})

db.collection.find({field:value,field2:value2}).skip(n).limit(1)

コレクションがクエリ用にインデックス化されている場合、高速である必要があります。

于 2012-12-17T08:46:11.730 に答える