Javascript 如何通过计算任何数组项在mongodb聚合中添加新字段

Javascript 如何通过计算任何数组项在mongodb聚合中添加新字段,javascript,mongodb,mongodb-query,aggregation-framework,aggregate,Javascript,Mongodb,Mongodb Query,Aggregation Framework,Aggregate,我正在努力找出如何在基于项目数组计算的聚合中添加一个新的status字段 目前,我在Angular前端通过询问集合和使用迭代每个元素来实现这一点。但是我想把计算转移到后端,我被MongoDB聚合卡住了 输入:每个订阅(每个用户一个)都有许多合同(每个月一个),我想从合同中计算订阅的状态 [ { "_id": "5b4d9a2fde57780a2e175agh", "user": "5a709da7c2ffc105hu47b254", "contracts": [

我正在努力找出如何在基于项目数组计算的聚合中添加一个新的
status
字段

目前,我在Angular前端通过询问集合和使用迭代每个元素来实现这一点。但是我想把计算转移到后端,我被MongoDB聚合卡住了

输入:每个订阅(每个用户一个)都有许多合同(每个月一个),我想从合同中计算订阅的
状态

[
  {
    "_id": "5b4d9a2fde57780a2e175agh",
    "user": "5a709da7c2ffc105hu47b254",
    "contracts": [
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-07-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 67,
        "isCanceled": false,
        "paymentFailed": false
      },
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-08-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 0,
        "isCanceled": false,
        "paymentFailed": false
      },
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-09-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 0,
        "isCanceled": false,
        "paymentFailed": false
      }
    ]
  }
]
输出:在这种情况下,取过去的合同,检查用户是否按照totalPrice所说的支付了(以及是否没有任何支付错误)。如果不是,则订阅的付款“待定”:

{
  "_id": "5b4d9a2fde57780a2e175agh",
  "user": "5a709da7c2ffc105hu47b254",
  "status": "PAYMENT_PENDING" // or "PAYMENT_ERROR" or "SUCCESS"…
}
但我无法按每个数组项进行计算:如果我尝试使用
“$contracts.$.totalPaid”
(“'FieldPath字段名不能以'$'开头”),它会给出一个错误

这是我的聚合步骤(仅测试两个
状态
条件):

我已经成功地从订阅的字段中计算了
状态
,但没有从它的合同数组中计算出来

[
  {
    "_id": "5b4d9a2fde57780a2e175agh",
    "user": "5a709da7c2ffc105hu47b254",
    "contracts": [
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-07-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 67,
        "isCanceled": false,
        "paymentFailed": false
      },
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-08-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 0,
        "isCanceled": false,
        "paymentFailed": false
      },
      {
        "_id": "5b4d9a2fde57780a2e175agh",
        "date": "2018-09-15T13:00:00.000Z",
        "totalPrice": 200,
        "totalPaid": 0,
        "isCanceled": false,
        "paymentFailed": false
      }
    ]
  }
]
如果有人能为我指出聚合框架的正确方向,我将不胜感激,就像我发现的其他示例/问题一样,
$sum
/calculate,但不要添加新字段


非常感谢。

我找到了一种方法:我不再直接在
$addFields
步骤中计算,而是执行更多步骤

请随意建议对聚合的改进,因为这是我的第一个大agrgegate:)

第一步:$match 我感兴趣的订阅条件。(使用你自己的)

步骤2:$lookup 加入每个订阅及其所有合同:

        $lookup: {
          // Join with subscriptioncontracts collection
          "from" : "subscriptioncontracts",
          "localField" : "_id",
          "foreignField" : "subscription",
          "as" : "contracts"
        }
第三步:放松 每个订阅合同制作一份文档:

        $unwind: {
          // Make one document per subscription contract
          path : "$contracts",
          preserveNullAndEmptyArrays : false // optional
        }
步骤4:$sort (我需要使用上一份/最新合同的一些特殊内容,因此我需要对它们进行排序)

第五步:$group 神奇之处在于:使用所有订阅合同(现在每个“合同”都在自己的文档中,而不是在一个数组中)在计算中添加新字段

我需要添加
“订阅”
,因为我需要将其作为响应进行投影

        $group: {
          // Calculate status from contracts (group by the same subscription _id)
          "_id": "$_id",
          "subscription": { "$first": "$$CURRENT" },
          "_lastContract": { $last: "$contracts" },

          "_statusPaymentPending": {
            $sum: { $cond: [
              { $and: [
                { $lt: [ "$contracts.paidAmount", "$contracts.checkout.totalPrice" ] },
                { $lt: [ "$contracts.subscriptionDate", new Date() ] },
                { $eq: [ "$contracts.paymentFailed", false ] },
                    { $eq: [ "$contracts.isCanceled", false ] }
              ]}, 1, 0
            ] }
          },

          "_statusPaymentFailed": {
            $sum: { $cond: [
              { $and: [
                { $lt: [ "$contracts.paidAmount", "$contracts.checkout.totalPrice" ] },
                { $lt: [ "$contracts.subscriptionDate", new Date() ] },
                { $eq: [ "$contracts.paymentFailed", true ] },
                    { $eq: [ "$contracts.isCanceled", false ] }
              ]}, 1, 0
            ] }
          }
        }
步骤6:$项目 在这里,我根据订阅数据(而不是合同)计算其他状态

第7步:项目 最后,我将所有状态合并到一个
“status”
字段中:

        $project: {
          "subscription": 1,
            // Condense all statuses into one Status field
            "status": {
              $cond: [
                "$_statusCanceled",
                'CANCELED',
                { $cond: [
                    "$_statusPaymentFailed",
                    'PAYMENT_ERROR',
                    { $cond: [
                        "$_statusPaymentPending",
                        'PAYMENT_PENDING',
                        { $cond: [
                            "$_statusUnsubscribed",
                            'UNSUBSCRIBED',
                            { $cond: [
                                "$_statusExtensionPending",
                                'PENDING_EXTEND_CONTRACT',
                                { $cond: [
                                    "$_statusFutureStart",
                                    'FUTURE_START',
                                    { $cond: [
                                        "$_statusFinished",
                                        'FINISHED',
                                        'OK'
                                    ]}
                                ]}
                            ]}
                        ]}
                    ]}
                ]}
              ]
            }
        }
待办事项 也许你可以改进我的流程:

  • 是否可以将
    subscription
    对象中的所有数据移动到根目录中(并伴有计算的
    状态
    字段),而不是使用
    订阅
    状态
    最终对象
  • 您是否看到了其他更好的方法来计算这个最终的
    状态
    字段,而不是有一个
    $group
    和两个
    $project

感谢您提出的任何改进建议

是否检查付款中的所有或任何数组元素?如果所有数组元素都有不同的值,那么状态是什么?Hi@Veeram我检查的数组元素都是过去的合同,也就是说,
{$lte:[“$contracts.date”,ISODate(“2018-08-24T18:32:50.958+0000”)}
(今天)。如果未满足
$cond
中的任何条件,只需输出
状态:“OK”
(为简洁起见,在本例中为“其他内容”)
        $project: {
          // Calculate other statuses
          "_id": "$_id",
          "subscription": "$subscription",
          "_statusCanceled": { $cond: [ "$subscription.isCanceled", true, false ] },
          "_statusFutureStart": { $cond: [ { $gte: [ "$subscription.subscriptionStartDate", new Date() ] }, true, false ] },
          "_statusUnsubscribed": { $cond: [ { $gte: [ "$subscription.subscriptionEndDate", new Date() ] }, true, false ] },
          "_statusFinished": {
            $cond: [
              { $and: [
                { $ne: [ "$subscription.subscriptionEndDate", undefined ] },
                { $lte: [ "$subscription.subscriptionEndDate", new Date() ] }
              ]},
              true,
              false 
            ]
          },
          "_statusPaymentPending": "$_statusPaymentPending",
          "_statusPaymentFailed": "$_statusPaymentFailed",
          "_statusExtensionPending": { $cond: [ { $lte: [ "$_lastContract.expirationDate", new Date() ] }, true, false ] }
        }
        $project: {
          "subscription": 1,
            // Condense all statuses into one Status field
            "status": {
              $cond: [
                "$_statusCanceled",
                'CANCELED',
                { $cond: [
                    "$_statusPaymentFailed",
                    'PAYMENT_ERROR',
                    { $cond: [
                        "$_statusPaymentPending",
                        'PAYMENT_PENDING',
                        { $cond: [
                            "$_statusUnsubscribed",
                            'UNSUBSCRIBED',
                            { $cond: [
                                "$_statusExtensionPending",
                                'PENDING_EXTEND_CONTRACT',
                                { $cond: [
                                    "$_statusFutureStart",
                                    'FUTURE_START',
                                    { $cond: [
                                        "$_statusFinished",
                                        'FINISHED',
                                        'OK'
                                    ]}
                                ]}
                            ]}
                        ]}
                    ]}
                ]}
              ]
            }
        }