Esiste un modo per eseguire in modo efficiente l'equivalente di DENSE_RANK in MongoDB?


8

SQL Server e Oracle dispongono entrambi delle funzioni DENSE_RANK. C'è un modo per fare qualcosa di simile in MongoDB senza dover ricorrere a MapReduce? In altre parole, supponiamo di avere una clausola select T-SQL come questa:

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

Qual è il modo migliore per fare la stessa cosa in MongoDB?

(Nota: questo è un ripasso della domanda MongoDB qui . Spero di ottenere più feedback dai DBA ...)


Domanda audace, davvero. Se trovi le risposte alle tue domande MongoDB soddisfacenti qui nel DBA.SE, ti preghiamo di far sapere agli altri di portare anche qui le loro domande e risposte. +1 !!!
RolandoMySQLDBA,

Risposte:


5

MongoDB non ha alcun concetto di classifica. Il più vicino che potrei trovare viene da qui :

Ecco alcuni dati di esempio:

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

Innanzitutto, trova il punteggio dell'utente "dave":

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

Quindi, conta quanti utenti hanno un punteggio più alto:

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

Dato che c'è 1 punteggio più alto, il rango di dave è 2 (basta aggiungere 1 al conteggio dei punteggi più alti per ottenere il rango).

Ovviamente, questo è tutt'altro che ideale. Tuttavia, MongoDB semplicemente non ha alcun tipo di funzionalità per questo poiché non è semplicemente progettato per questo tipo di query.


2
In realtà, ha la funzionalità tramite MapReduce, è solo lento.
kgriffs

@Kurt Oh, dovresti pubblicarlo come risposta! Gli internet lo apprezzerebbero davvero, ne sono sicuro. ;)
Richard,

5

Dopo alcuni esperimenti, ho scoperto che è possibile creare una funzione di classificazione basata su MapReduce, supponendo che il set di risultati possa adattarsi alla dimensione massima del documento.

Ad esempio, supponiamo che io abbia una collezione come questa:

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

Posso eseguire l'equivalente approssimativo di un DENSE_RANK in questo modo:

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

Per fare un confronto, ecco l'approccio "ingenuo" menzionato altrove:

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

Ho confrontato entrambi gli approcci su una singola istanza di MongoDB 1.8.2 usando il seguente codice:

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

Mentre MapReduce è stato più veloce di quanto mi aspettassi, l'approccio ingenuo l'ha fatto esplodere per dimensioni di raccolta più grandi, soprattutto una volta che la cache è stata riscaldata:

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

Quindi per ora, sembra che l'approccio ingenuo sia la strada da percorrere, anche se sarò interessato a vedere se la storia cambierà entro la fine dell'anno mentre il team MongoDB continua a migliorare le prestazioni di MapReduce.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.