ページ

2012年3月11日日曜日

MongoDBのコレクションからランダムなドキュメントを取り出す

色々間違ってそうで不安だ。

http://eric.lubow.org/2010/databases/mongodb/getting-a-random-record-from-a-mongodb-collection/
ググるとこういうやり方が出て来るのだけど、実際やってみるとこれではランダムにならない。

上のやり方を僕の理解でなんとなく説明してみると
{id:1, random:9}
{id:2, random:2}
{id:3, random:5}
{id:4, random:1}
みたいな、あらかじめそれぞれのドキュメントがランダムな値を持ってるコレクションがあって
db.docs.findOne( { random : { $gte : rand } } )
みたいな感じでドキュメントを取り出すと、randよりも大きいrandom(先述したランダムな値)を持ってるドキュメントを一つ取り出す。

このrandってのはドキュメントを取り出そうとするたびに変わる値で、それゆえにrandよりも大きいrandomを持つドキュメントは毎回変わるということ。

問題はここから。

findOne()ってのはfind()したやつの中の1個を返して来る。
find()で返ってくるのはカーソルっていうハッシュみたいなやつらしく、 どうやら中身がid順で並んでる。

例えばrandが8のとき、8より大きいrandomを持つのはid:1のドキュメントだけなのでそれが取り出される。これはいい。
次に、randが3のとき、この場合はid:1とid:3が該当する。

この2つ
{:id1, random:9}
{:id3, random:5}

このとき出てきて欲しいのは、3より大きいrandomの中では一番小さい5を持つドキュメント。
でも、実際取り出されるのは並んでるのの1個目なのでid:1のドキュメント。

つまり、idが若くてrandomの値が大きいやつに偏って取り出されてしまう。

どうしたものかと考えていたらこんな解決法があった。
http://cookbook.mongodb.org/patterns/random-attribute/#comment-452243980

地理空間のインデックスを利用して、randから"一番近い"randomを持ってるやつを検索取得するようにしてる。


最初にrandomフィールドに地理空間のインデックスを作成しておく。
db.docs.ensureIndex( { random: '2d' } )

そのあとの流れは上のと大体同じ。
// それぞれのX軸の値をランダムにする
for ( i = 0; i < 10; ++i ) db.docs.insert( { key: i, random: [Math.random(), 0] } );

// $nearを使うことでランダムな値に近いやつを取れる
db.docs.findOne( {random : { $near : [Math.random(), 0] } } ) 

// 任意の個数取り出すこともできる
db.docs.find( { random : { $near : [Math.random(), 0] } } ).limit( 4 ) 


あと、そんなに早くなくてもいいからとりあえず1個取り出したいようなときはこれだけでもいける。

参考: http://stackoverflow.com/questions/2824157/random-record-from-mongodb/2824166#2824166

0 件のコメント:

コメントを投稿