流星的平均聚合查询

时间:2013-08-29 20:40:28

标签: mongodb meteor aggregation-framework

好的,仍然在我的玩具应用程序中,我想找出一组车主的里程表的平均里程数。这在客户端非常简单,但不能扩展。对?但是在服务器上,我并没有完全看到如何实现它。

问题:

  1. 如何在服务器上实现某些功能,然后在客户端上使用它?
  2. 如何使用mongo的$ avg聚合函数来利用其优化的聚合函数?
  3. 或者(2)如何在服务器上执行map / reduce并将其提供给客户端?
  4. @HubertOG的建议是使用Meteor.call,这是有意义的,我这样做了:

    # Client side
    Template.mileage.average_miles = ->
      answer = null
      Meteor.call "average_mileage", (error, result) ->
        console.log "got average mileage result #{result}"
        answer = result
      console.log "but wait, answer = #{answer}"
      answer
    
    # Server side
    Meteor.methods average_mileage: ->
      console.log "server mileage called"
      total = count = 0
      r = Mileage.find({}).forEach (mileage) ->
        total += mileage.mileage
        count += 1
      console.log "server about to return #{total / count}"
      total / count
    

    这似乎工作正常,但它并不是因为我可以告诉Meteor.call是一个异步调用,而answer将始终是一个空返回。处理服务器上的东西似乎是一个常见的用例,我必须忽略一些东西。那会是什么?

    谢谢!

4 个答案:

答案 0 :(得分:29)

从Meteor 0.6.5开始,集合API还不支持聚合查询,因为没有(直接)方式对它们进行实时更新。但是,您仍然可以自己编写它们,并在Meteor.publish中使它们可用,但结果将是静态的。在我看来,这样做仍然是可取的,因为您可以合并多个聚合并使用客户端集合API。

Meteor.publish("someAggregation", function (args) {
    var sub = this;
    // This works for Meteor 0.6.5
    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    // Your arguments to Mongo's aggregation. Make these however you want.
    var pipeline = [
        { $match: doSomethingWith(args) },
        { $group: {
            _id: whatWeAreGroupingWith(args),
            count: { $sum: 1 }
        }}
    ];

    db.collection("server_collection_name").aggregate(        
        pipeline,
        // Need to wrap the callback so it gets called in a Fiber.
        Meteor.bindEnvironment(
            function(err, result) {
                // Add each of the results to the subscription.
                _.each(result, function(e) {
                    // Generate a random disposable id for aggregated documents
                    sub.added("client_collection_name", Random.id(), {
                        key: e._id.somethingOfInterest,                        
                        count: e.count
                    });
                });
                sub.ready();
            },
            function(error) {
                Meteor._debug( "Error doing aggregation: " + error);
            }
        )
    );
});

以上是分组/计数聚合的示例。有些注意事项:

  • 执行此操作时,您自然会在server_collection_name上进行聚合,并将结果推送到名为client_collection_name的其他集合。
  • 此订阅不会生效,并且可能会在参数更改时更新,因此我们使用一个非常简单的循环来推送所有结果。
  • 聚合的结果没有Mongo ObjectID,所以我们自己生成一些任意的。
  • 聚合的回调需要包含在光纤中。我在这里使用Meteor.bindEnvironment,但也可以使用Future进行更低级别的控制。

如果您开始合并这些出版物的结果,则需要仔细考虑随机生成的ID如何影响合并框。但是,直接实现这个只是一个标准的数据库查询,除了使用Meteor API客户端更方便。

TL; DR版:几乎在您从服务器推送数据的任何时候,publish都优于method

有关不同聚合方式的详情, check out this post

答案 1 :(得分:2)

我使用'聚合'方法。 (版本0.7.x)

if(Meteor.isServer){
Future = Npm.require('fibers/future');
Meteor.methods({
    'aggregate' : function(param){
        var fut = new Future();
        MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){
            fut.return(result);
        });
        return fut.wait();
    }
    ,'test':function(param){
        var _param = {
            pipe : [
            { $unwind:'$data' },
            { $match:{ 
                'data.y':"2031",
                'data.m':'01',
                'data.d':'01'
            }},
            { $project : {
                '_id':0
                ,'project_id'               : "$project_id"
                ,'idx'                      : "$data.idx"
                ,'y'                        : '$data.y'
                ,'m'                        : '$data.m'
                ,'d'                        : '$data.d'
            }}
        ],
            collection:"yourCollection"
        }
        Meteor.call('aggregate',_param);
    }
});

}

答案 2 :(得分:1)

您可以使用Meteor.methods

// server
Meteor.methods({
  average: function() {
    ...
    return something;
  },

});

// client

var _avg = {                      /* Create an object to store value and dependency */
  dep: new Deps.Dependency();
};

Template.mileage.rendered = function() {
  _avg.init = true;
};

Template.mileage.averageMiles = function() {
  _avg.dep.depend();              /* Make the function rerun when _avg.dep is touched */
  if(_avg.init) {                 /* Fetch the value from the server if not yet done */
    _avg.init = false; 
    Meteor.call('average', function(error, result) {
      _avg.val = result;
      _avg.dep.changed();         /* Rerun the helper */
    });
  }
  return _avg.val;
});

答案 3 :(得分:1)

如果您想要反应,请使用Meteor.publish代替Meteor.call。在docs中有一个例子,他们在那里发布给定房间中的消息数量(就在this.userId的文档上方),你应该可以做类似的事情。