Apakah ada cara untuk melakukan efisiensi yang setara dengan DENSE_RANK di MongoDB?

8

SQL Server dan Oracle keduanya memiliki fungsi DENSE_RANK. Apakah ada cara untuk melakukan sesuatu yang serupa di MongoDB tanpa harus menggunakan MapReduce? Dengan kata lain, misalkan Anda memiliki klausa pilih T-SQL seperti ini:

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

Apa cara terbaik untuk melakukan hal yang sama di MongoDB?

(Catatan: Ini adalah repost pertanyaan MongoDB di sini . Saya berharap mendapatkan lebih banyak umpan balik dari DBA ...)

kgriff
sumber
Pertanyaan yang berani, memang. Jika Anda menemukan jawaban untuk pertanyaan MongoDB Anda memuaskan di sini di DBA.SE, beri tahu orang lain untuk membawa pertanyaan dan jawaban mereka di sini juga. +1 !!!
RolandoMySQLDBA

Jawaban:

5

MongoDB tidak memiliki konsep peringkat apa pun. Yang paling dekat yang bisa saya temukan berasal dari sini :

Berikut beberapa contoh data:

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

Pertama, temukan skor pengguna "dave":

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

Lalu, hitung berapa banyak pengguna yang memiliki skor lebih tinggi:

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

Karena ada 1 skor lebih tinggi, peringkat dave adalah 2 (cukup tambahkan 1 ke jumlah skor yang lebih tinggi untuk mendapatkan peringkat).

Jelas, ini jauh dari ideal. Namun, MongoDB sama sekali tidak memiliki jenis fungsi apa pun untuk ini karena itu hanya tidak dirancang untuk jenis permintaan ini.

Richard
sumber
2
Sebenarnya, ia memiliki fungsi via MapReduce, hanya saja lambat.
kgriffs
@Kurt Oh, Anda harus memposting itu sebagai jawabannya! Internet akan sangat menghargainya, saya yakin. ;)
Richard
5

Setelah beberapa percobaan, saya menemukan bahwa adalah mungkin untuk membangun fungsi peringkat berdasarkan MapReduce, dengan asumsi set hasil dapat sesuai dengan ukuran dokumen maks.

Sebagai contoh, misalkan saya memiliki koleksi seperti ini:

{ 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" }
...

Saya dapat melakukan yang setara dengan DENSE_RANK seperti:

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

Sebagai perbandingan, berikut adalah pendekatan "naif" yang disebutkan di tempat lain:

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

Saya membandingkan kedua pendekatan pada satu contoh MongoDB 1.8.2 menggunakan kode berikut:

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

Sementara MapReduce lebih cepat dari yang saya harapkan, pendekatan naif meledakkannya dari air untuk ukuran pengumpulan yang lebih besar, terutama sekali cache dihangatkan:

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

Jadi untuk saat ini, sepertinya pendekatan naif adalah jalan yang harus ditempuh, meskipun saya akan tertarik untuk melihat apakah ceritanya berubah akhir tahun ini karena tim MongoDB terus meningkatkan kinerja MapReduce.

kgriff
sumber