Có cách nào để thực hiện hiệu quả tương đương DENSE_RANK trong MongoDB không?


8

Cả SQL Server và Oracle đều có chức năng DENSE_RANK. Có cách nào để làm một cái gì đó tương tự trong MongoDB mà không cần phải dùng đến MapReduce không? Nói cách khác, giả sử bạn có mệnh đề chọn T-SQL như thế này:

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

Cách tốt nhất để làm điều tương tự trong MongoDB là gì?

(Lưu ý: Đây là một bài đăng lại câu hỏi MongoDB ở đây . Tôi hy vọng sẽ nhận được nhiều phản hồi hơn từ các DBA ...)


Câu hỏi táo bạo, thực sự. Nếu bạn tìm thấy câu trả lời cho các câu hỏi MongoDB của bạn thỏa đáng ở đây trong DBA.SE, vui lòng cho người khác biết để mang câu hỏi và câu trả lời của họ ở đây. +1 !!!
RolandoMySQLDBA

Câu trả lời:


5

MongoDB không có bất kỳ khái niệm về xếp hạng. Gần nhất tôi có thể tìm thấy đến từ đây :

Đây là một số dữ liệu mẫu:

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

Đầu tiên, tìm điểm của người dùng "dave":

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

Sau đó, đếm xem có bao nhiêu người dùng có điểm cao hơn:

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

Vì có 1 điểm cao hơn, thứ hạng của dave là 2 (chỉ cần thêm 1 vào số điểm cao hơn để có được thứ hạng).

Rõ ràng, điều này là xa lý tưởng. Tuy nhiên, MongoDB đơn giản là không có bất kỳ loại chức năng nào cho việc này vì đơn giản là nó không được thiết kế cho loại truy vấn này.


2
Trên thực tế, nó có chức năng thông qua MapReduce, nó chỉ chậm.
kgriff

@Kurt ơi, bạn nên đăng nó làm câu trả lời! Các internets sẽ thực sự đánh giá cao nó, tôi chắc chắn. ;)
Richard

5

Sau một số thử nghiệm, tôi thấy rằng có thể xây dựng hàm xếp hạng dựa trên MapReduce, giả sử tập kết quả có thể vừa với kích thước tài liệu tối đa.

Ví dụ: giả sử tôi có một bộ sưu tập như thế này:

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

Tôi có thể thực hiện tương đương thô của DENSE_RANK như vậy:

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

Để so sánh, đây là cách tiếp cận "ngây thơ" được đề cập ở nơi khác:

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

Tôi đã điểm chuẩn cả hai cách tiếp cận trên một phiên bản MongoDB 1.8.2 bằng mã sau:

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

Mặc dù MapReduce nhanh hơn tôi mong đợi, cách tiếp cận ngây thơ đã thổi nó ra khỏi nước với kích thước bộ sưu tập lớn hơn, đặc biệt là khi bộ đệm được làm nóng:

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

Vì vậy, hiện tại, có vẻ như cách tiếp cận ngây thơ là hướng đi, mặc dù tôi sẽ quan tâm xem liệu câu chuyện có thay đổi vào cuối năm nay hay không khi nhóm MongoDB tiếp tục cải thiện hiệu suất MapReduce.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.