在$ cond语句中使用MongoDBs聚合,从分组和总和月收入中排除集合失败

时间:2014-11-19 08:24:56

标签: mongodb business-intelligence

我的数据:

db.customers:

{ "_id" : "email@address.de", "since" : ISODate("2010-12-08T09:26:33Z") }

db.orders

{ "_id" : "201234224", "order_date" : ISODate("2010-12-08T09:26:33Z"), "net_revenue" : "26.8400", "customer_id" : "email@address.de" }
{ "_id" : "201223245", "order_date" : ISODate("2011-04-16T16:09:17Z"), "net_revenue" : "26.8400", "customer_id" : "email@address.de" }

现在我想总结每月回归客户的net_revenue,这意味着我必须排除等于since日期的初始订单。我带来了以下声明

db.orders.aggregate( [
{
     $project:
       {
         _id:
           {
             $cond: { if: { $eq: [ db.customers.find({_id:"$customer_id"},{ _id:0,since:1 }), "$order_date" ] }, then: 0, else: 1 }
           }
       }
  },
{ $group : {
    _id: {
        year : { $year: "$order_date" },        
        month : { $month: "$order_date" }
    },
    count: { $sum: 1 },
    net_revenue: { $sum: 1 }
}}
]

);

我不确定如何对net_revenue进行求和,但我的$cond已失败

errmsg" : "exception: the $cond operator requires an array of 3 operands",

我需要其他的吗??

1 个答案:

答案 0 :(得分:0)

首先,正如已经评论过的那样:您的net_revenue是一个字符串,您无法创建字符串总和。首先,我们需要将所有net_revenue字段转换为浮点数,以便能够处理它们:

> var bulk = db.orders.initializeUnorderedBulkOp()
> db.orders.find().forEach(function(order){
  order.net_revenue = parseFloat(order.net_revenue);   
  bulk.find({_id:order._id}).updateOne(order);
})
> bulk.execute()

您应该确保将net_revenue保存为浮点数,而不是字符串。

现在,您需要的实际数据。如果你改写它,你想要

  

拥有多个订单的所有客户的所有net_revenues的总数

这转换为:

db.orders.aggregate(
  {$group: { _id:"$customer_id", orders:{$sum:1}, total_revenue:{$sum:"$net_revenue"} } },
  { $match: {orders:{ $gte:2 } } }
)

首先,我们按$customer_id对所有订单进行分组,创建新的字段顺序,对于处理的每个文档,该字段顺序增加1。此外,我们创建新字段total_revenue,其增加了每个处理文档的net_revenue值。最后但同样重要的是,我们只希望那些已经下了多个订单的客户。上述聚合的输出符合预期:

{ "_id" : "email@address.de", "orders" : 2, "total_revenue" : 53.68 }

修改

根据禁区,第一个值无法计算。由于我们不能在聚合框架中跳过组内,我们需要使用map reduce和一种丑陋的黑客。

db.orders.mapReduce(
  // Map
  function(){

    // We need to have all values
    emit(this.customer_id,this.net_revenue);

  },

  // Reduce
  function(customer,values){
    var recurring_revenue = 0;

    // We build the sum for all key which have multiple values
    for(var idx = 1; idx < values.length; idx++) {
      recurring_revenue += values[idx];
    }

    // here is the hack:
    // Since the reduce phase is only run if a key has multiple values
    // We need to make sure that the recurring revenues are can be queried in our
    // output collection
    var reduced = {
       "recurring":recurring_revenue,
       "orders":values.length
    };

    return reduced
  },
  // Options for map/reduce
  {
      // Since we want to skip the first order by date
      // we need to make sure the original documents are fed into the map
      // function in order of date
      sort:{"order_date":1},
      out:"recurring_revenues"
   })

现在,为了让所有具有经常性收入的客户,我们需要查询我们的out集合

db.recurring_revenues.find({"value.orders":{$gt:1}})

应返回

{ "_id" : "email@address.de", "value" : { "recurring" : 26.84, "orders" : 2 } }

根据给出的示例数据。如果有多个客户有多个订单,那么他们将被退回。

编辑2

在2.4中,您没有批量操作。转换可以使用

完成
db.order.find().forEach(function(order) {
  var rev =  parseFloat(order.net_revenue);
  db.order.update({_id:order._id},{$set:{"net_revenue":rev}});
}

在大型系列中会相当慢。