MongoDB查询结果中的自定义字段

时间:2012-11-21 12:23:20

标签: node.js mongodb mongoose

在习惯了SQL之后,我遇到了mongoDB这个问题。 首先,我使用的是猫鼬。

现在,问题。我有一个名为User的集合。

var UserSchema = new Schema ({
    id : ObjectId,
    name : {type : String, trim : true, required : true},
    email: {type:String, trim:true, required: true, index: { unique: true }},
    password: {type:String, required: true, set: passwordToMD5},
    age: {type:Number, min: 18, required: true, default: 18},
    gender: {type: Number, default:0, required: true},

    height: {type: Number, default:180, min: 140, max: 220},
    _eye_color: {type: ObjectId, default: null},
    location: {
            lon: {type: Number, default: 0},
            lat: {type: Number, default: 0}
    },
    status: {type:Number, required: true, default:0}
    },{
        toObject: { virtuals: true },
        toJSON: { virtuals: true },
        collection:"user"});

现在我需要从这个集合中选择所有用户并按特殊属性(比如“rank”)对它们进行排序。这个等级是根据它们与点的距离,年龄与给定年龄相比等的某些逻辑来计算的......

所以现在我想知道如何选择这个等级然后在排序中使用它?我尝试使用虚拟数据,它们可以方便地计算其他信息,但遗憾的是,无法通过虚拟字段对find()结果进行排序。 当然我可以在虚拟中计算这个等级,然后选择所有记录,然后在回调中,做一些javascript。但在这种情况下,当我选择所有用户然后排序然后限制时,javascript部分可能需要太长时间...... 我想使用 mapreduce ,但我不确定它会做我想要的。 如果我的任务可以在 mongoDB / mongoose 中完成,有人可以给我一个提示吗?

编辑1

我也尝试使用聚合框架,起初它似乎是具有$project能力的最佳解决方案。但是,当我需要进行排名计算时,我发现聚合不支持很多数学函数,如sincossqrt。而且在投影中也不可能使用预定义的常用javascript函数。我的意思是,函数被调用,但我无法将当前记录字段传递给它。

{$project: {
  distance_from_user: mUtils.getDistance(point, this.location)
}

内部功能第二个attr是“未定义”。

所以我想用聚合框架进行排名计算是不可能的。

编辑2 好的,我知道每个人都告诉我不要使用mapreduce,因为它对实时查询不好,但由于我不能使用聚合,我想我会尝试mapreduce。所以,假设我有这张地图缩小。

function map() {
            emit(1, // Or put a GROUP BY key here
                {name: this.name, // the field you want stats for
                    age: this.age,
                    lat: this.location.lat,
                    lon: this.location.lon,
                    distance:0,
                    rank:0

                });
        }

        function reduce(key, values) {


            return val;
        }

        function finalize(key, value){

            return value;
        }


        var command = {'mapreduce': "user", 'map': map.toString(), 'reduce': reduce.toString(), query:{$and: [{gender: user_params.gender}, {_id: {$ne: current_user_id}}]}, 'out': {inline:1}};

        mongoose.connection.db.executeDbCommand(command, function(error, result){
            if(error) {
                log(error);
                return;
            }
            log(result);
            return;
        });

我应该在减少(或者更改地图)中为每个用户计算排名?

3 个答案:

答案 0 :(得分:1)

唯一真正的解决方案是计算每个文档的排名并将其存储在文档中。由于只要文档中的值保持不变,此值就会保持不变,只要您更新影响它的字段,就可以简单地计算此值。

Map / reduce肯定不是一个很好的解决方案,也不是任何其他类型的聚合。如果您正在使用MongoDB,那么预先计算您的排名并将其与文档一起存储是唯一可扩展的选项。

答案 1 :(得分:1)

你知道这样的事情所需的计算量 - 如果你每次用户登录时都这样做,那么当很多人在较短的时间内登录时你会有一些有趣的负载峰值 - 以及你的页面(接口)将受到严重的资源限制(这是不好的) 我推荐你一些不同的东西 - 保持每个登录用户的排名并间隔更新它们:保持“短会话”和“长会话”(长会话 - 你在网络浏览器中使用的那个 - 短 - “在线,目前使用网站“)并定期为”短期活跃“用户生成排名,很少为长期会话登录。像每五分钟一样。更多的可扩展 - 如果用户不满意他没有计算他的排名 - 你可能总是调整系统以按需计算他的排名。
在这种情况下你可能会使用mapredurce - 你的map函数应该只发出你需要的数据来计算给定用户的等级(如年龄,纬度,长度,你需要的任何东西)和测试用户的结果(等级)它是空的)。对于reduce函数,你需要查看mapreduce的排序(它在很大程度上取决于你创建排名的方式) - 你还要计算其他用户的排名(或某种子值)。

答案 2 :(得分:0)

它看起来像是MongoDB + Hadoop的一个很好的用例。

presentation显示了这种组合的一些可能性。