MongoDBでDENSE_RANKに相当するものを効率的に実行する方法はありますか?


8

SQL ServerとOracleの両方にDENSE_RANK関数があります。MapReduceに頼ることなく、MongoDBで同様のことを行う方法はありますか?つまり、次のようなT-SQL選択句があるとします。

SELECT DENSE_RANK() OVER(ORDER BY SomeField DESC) SomeRank

MongoDBで同じことをする最良の方法は何ですか?

(注:これは、MongoDBの質問の再投稿です。DBA からのフィードバックをもっと期待しています...)


確かに大胆な質問です。MongoDBの質問に対する回答がここDBA.SEで満足のいくものである場合は、他の人にも質問と回答をここに持ってくるように知らせてください。+1 !!!
RolandoMySQLDBA 2011

回答:


5

MongoDBにはランキングの概念はありません。私が見つけることができる最も近いものはここから来ます

ここにいくつかのサンプルデータがあります:

 > db.scoreboard.find()`
 { "_id" : ObjectId("4d99f71450f0ae2165669ea9"), "user" : "dave", "score" : 4 }
 { "_id" : ObjectId("4d99f71b50f0ae2165669eaa"), "user" : "steve", "score" : 5 }`
 { "_id" : ObjectId("4d99f72350f0ae2165669eab"), "user" : "tom", "score" : 3 }

まず、ユーザー「dave」のスコアを見つけます。

 db.scoreboard.find({ user : "dave" }, { score : 1 }) { "_id" : ObjectId("4d99f71450f0ae2165669ea9"), "score" : 4 }

次に、スコアが高いユーザーの数を数えます。

 db.scoreboard.find({ score : { $gt : 4 }}).count() 
 1

より高いスコアが1つあるため、daveのランクは2です(ランクを取得するには、より高いスコアのカウントに1を追加するだけです)。

明らかに、これは理想からかけ離れています。ただし、MongoDBはこのタイプのクエリ用に設計されていないため、MongoDBにはこの機能はありません。


2
実際、MapReduceを介した機能はありますが、遅いだけです。
kgriffs

@カートああ、あなたはそれを答えとして投稿するべきです!インターネットはそれを本当に感謝するでしょう、私は確信しています。;)
Richard

5

いくつかの実験の結果、結果セットが最大ドキュメントサイズに収まると仮定すると、MapReduceに基づいてランキング関数を構築できることがわかりました。

たとえば、次のようなコレクションがあるとします。

{ player: "joe", points: 1000, foo: 10, bar: 20, bang: "some text" }
{ player: "susan", points: 2000, foo: 10, bar: 20, bang: "some text" }
{ player: "joe", points: 1500, foo: 10, bar: 20, bang: "some text" }
{ player: "ben", points: 500, foo: 10, bar: 20, bang: "some text" }
...

私はDENSE_RANKの大まかな同等物を次のように実行できます:

var m = function() { 
  ++g_counter; 

  if ((this.player == "joe") && (g_scores.length != g_fake_limit)) { 
    g_scores.push({
      player: this.player, 
      points: this.points, 
      foo: this.foo,
      bar: this.bar,
      bang: this.bang,
      rank: g_counter
    });   
  }

  if (g_counter == g_final)
  {
    emit(this._id, g_counter);
  }
}}


var r = function (k, v) { }
var f = function(k, v) { return g_scores; }

var test_mapreduce = function (limit) {
  var total_scores = db.scores.count();

  return db.scores.mapReduce(m, r, {
    out: { inline: 1 }, 
    sort: { points: -1 }, 
    finalize: f, 
    limit: total_scores, 
    verbose: true,
    scope: {
      g_counter: 0, 
      g_final: total_scores, 
      g_fake_limit: limit, 
      g_scores:[]
    }
  }).results[0].value;
}

比較のために、他の場所で言及されている「素朴な」アプローチを以下に示します。

var test_naive = function(limit) {
  var cursor = db.scores.find({player: "joe"}).limit(limit).sort({points: -1});
  var scores = [];

  cursor.forEach(function(score) {
    score.rank = db.scores.count({points: {"$gt": score.points}}) + 1;
    scores.push(score);
  });

  return scores;
}

次のコードを使用して、MongoDB 1.8.2の単一インスタンスで両方のアプローチをベンチマークしました。

var rand = function(max) {
  return Math.floor(Math.random() * max);
}

var create_score = function() {
  var names = ["joe", "ben", "susan", "kevin", "lucy"]
  return { player: names[rand(names.length)], points: rand(1000000), foo: 10, bar: 20, bang: "some kind of example text"};
}

var init_collection = function(total_records) {
  db.scores.drop();

  for (var i = 0; i != total_records; ++i) {
    db.scores.insert(create_score());
  }

  db.scores.createIndex({points: -1})
}


var benchmark = function(test, count, limit) {
  init_collection(count);

  var durations = [];
  for (var i = 0; i != 5; ++i) {
    var start = new Date;
    result = test(limit)
    var stop = new Date;

    durations.push(stop - start);
  }

  db.scores.drop();

  return durations;
}

MapReduceは思ったよりも高速でしたが、特にキャッシュがウォームアップされた後は、単純なアプローチにより、コレクションサイズが大きくなると水面から吹き飛ばされました。

> benchmark(test_naive, 1000, 50);
[ 22, 16, 17, 16, 17 ]
> benchmark(test_mapreduce, 1000, 50);
[ 16, 15, 14, 11, 14 ]
> 
> benchmark(test_naive, 10000, 50);
[ 56, 16, 17, 16, 17 ]
> benchmark(test_mapreduce, 10000, 50);
[ 154, 109, 116, 109, 109 ]
> 
> benchmark(test_naive, 100000, 50);
[ 492, 15, 18, 17, 16 ]
> benchmark(test_mapreduce, 100000, 50);
[ 1595, 1071, 1099, 1108, 1070 ]
> 
> benchmark(test_naive, 1000000, 50);
[ 6600, 16, 15, 16, 24 ]
> benchmark(test_mapreduce, 1000000, 50);
[ 17405, 10725, 10768, 10779, 11113 ]

とりあえず、今のところ、素朴なアプローチが適しているように見えますが、MongoDBチームがMapReduceのパフォーマンスを向上させ続ける今年の後半に話が変わるかどうかに興味があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.