kgr*_*ffs 8 nosql mongodb sql-server
SQL Server 和 Oracle 都有 DENSE_RANK 函数。有没有办法在 MongoDB 中做类似的事情而不必求助于 MapReduce?换句话说,假设您有一个像这样的 T-SQL select 子句:
SELECT DENSE_RANK() OVER(ORDER BY SomeField DESC) SomeRank
Run Code Online (Sandbox Code Playgroud)
在 MongoDB 中做同样事情的最佳方法是什么?
(注意:这是这里对MongoDB 问题的重新发布。我希望从 DBA 那里得到更多反馈......)
MongoDB 没有任何排名的概念。我能找到的最接近的来自这里:
以下是一些示例数据:
Run Code Online (Sandbox Code Playgroud)> 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”的得分:
Run Code Online (Sandbox Code Playgroud)db.scoreboard.find({ user : "dave" }, { score : 1 }) { "_id" : ObjectId("4d99f71450f0ae2165669ea9"), "score" : 4 }
然后,计算有多少用户具有更高的分数:
Run Code Online (Sandbox Code Playgroud)db.scoreboard.find({ score : { $gt : 4 }}).count() 1
由于有 1 个更高的分数,因此 dave 的排名为 2(只需将更高的分数加 1 即可获得排名)。
显然,这远非理想。然而,MongoDB 根本没有任何类型的功能,因为它根本不是为这种类型的查询而设计的。
经过一些实验,我发现可以基于 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" }
...
Run Code Online (Sandbox Code Playgroud)
我可以像这样执行 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;
}
Run Code Online (Sandbox Code Playgroud)
为了比较,这里是其他地方提到的“幼稚”方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
我使用以下代码在 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;
}
Run Code Online (Sandbox Code Playgroud)
虽然 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 ]
Run Code Online (Sandbox Code Playgroud)
因此,就目前而言,看起来幼稚的方法是可行的方法,尽管随着 MongoDB 团队继续提高 MapReduce 性能,我很想看看今年晚些时候情况是否会发生变化。
归档时间: |
|
查看次数: |
1143 次 |
最近记录: |