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

标签 javascript mongodb mongodb-query aggregation-framework aggregate

我正在努力寻找如何根据对项目数组的计算在聚合中添加新的 status 字段。

目前,我在 Angular 前端通过询问两个集合并使用 _.some() method of Lodash 迭代每个元素来做到这一点.但我想将计算移至后端,但我受困于 MongoDB 聚合。

输入:每个订阅(每个用户一个)有很多契约(Contract)(每个月一个),我想根据契约(Contract)计算订阅的状态

[
  {
    "_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
      }
    ]
  }
]

输出:在这种情况下,获取过去的契约(Contract)并检查用户是否按照 totalPrice 所述支付了费用(以及是否没有任何支付错误)。如果不是,则订阅付款“待定”:

{
  "_id": "5b4d9a2fde57780a2e175agh",
  "user": "5a709da7c2ffc105hu47b254",
  "status": "PAYMENT_PENDING" // or "PAYMENT_ERROR" or "SUCCESS"…
}

但我无法按每个数组项进行计算:如果我尝试使用 "$contracts.$.totalPaid"(“'FieldPath 字段名称不能以'$'开头,它会出错。 '")

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

$addFields: {
  "status": {
    $cond: [
      { $and: [
        { $lt: [ "$contracts.totalPaid", "$contracts.totalPrice" ]},
        { $eq: [ "$contracts.paymentFailed", true ] },
        { $lte: [ "$contracts.date", ISODate("2018-08-24T18:32:50.958+0000") ]},
        { $eq: [ "$contracts.2.isCanceled", false ] }
      ]},
      'PAYMENT_ERROR',
      { $cond: [
        { $and: [
          { $lt: [ "$contracts.paidAmount", "$contracts.checkout.totalPrice" ]},
          //{ $eq: [ "$contracts.paymentFailed", false ] },
          //{ $lte: [ "$contracts.subscriptionDate", ISODate("2018-08-24T18:32:50.958+0000") ]},
          { $eq: [ "$contracts.isCanceled", true ] }
        ]},
        'PAYMENT_PENDING',
        'SOMETHING_ELSE'
      ]}
    ]
  }
}

我已经成功地从订阅的字段中计算出状态,但不是从它的契约(Contract)数组中计算出来的。

如果有人能用聚合框架指出我正确的方向,我将不胜感激,就像我发现的其他示例/问题一样,$sum/calculate 但不添加新字段。

非常感谢。

最佳答案

我找到了一个方法:而不是直接在$addFields中计算步骤,我再做几个步骤。

请随时提出改进聚合的建议,因为这是我的第一个大聚合:)

第 1 步:$匹配

我感兴趣的订阅条件。(使用您自己的)

第 2 步:$lookup

加入每个订阅及其所有合约:

        $lookup: {
          // Join with subscriptioncontracts collection
          "from" : "subscriptioncontracts",
          "localField" : "_id",
          "foreignField" : "subscription",
          "as" : "contracts"
        }

第 3 步:$展开

每个订阅契约(Contract)制作一份文件:

        $unwind: {
          // Make one document per subscription contract
          path : "$contracts",
          preserveNullAndEmptyArrays : false // optional
        }

第四步:$排序

(我需要一些特别的东西,使用最新的/最现代的契约(Contract),所以我需要对它们进行排序)

        $sort: {
          // Assure the last contract if the most modern
          "_id": 1,
          "contracts.subscriptionDate": 1
        }

第 5 步:$组

神奇之处在于:通过使用所有订阅契约(Contract)的计算添加新字段(现在每个“契约(Contract)”都在其自己的文档中,而不是在数组中)

我需要添加 “subscription”因为我需要将其投影为响应。

        $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 步:$项目

这里我从订阅数据(不是合约)计算其他状态

        $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 ] }
        }

第七步:$项目

最后,我将所有状态合并到一个 “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'
                                    ]}
                                ]}
                            ]}
                        ]}
                    ]}
                ]}
              ]
            }
        }

待办事项

也许你可以改进我的流程:

  • 而不是 subscriptionstatus最终对象,是否可以从 subscription 中移动所有数据?对象到根(伴随着计算的 status 字段)?
  • 您是否看到其他更好的方法来计算这个最终的 status字段,而不是 $group和两个 $project

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

关于javascript - 如何通过计算任何数组项在 mongodb 聚合中添加新字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52000151/

相关文章:

javascript - Mongo 查询如果字段不存在。如果存在,则必须在一个范围内

javascript - 在单独的对象中使用reduce进行对象解构

python - 如何在 Python 中提取 mongoDB 文档的最后一个 objectID?

mongodb - Dataloader 没有返回相同长度的数组?

mongodb - java中的mongodb查询

java - 如何使用 Java 在 MongoDB 中将子文档(如果不存在)添加到数组字段?

MongoDB - 将简单字段更改为对象

javascript - AngularJS $范围未定义

javascript - 使用将呈现 iframe 的 HTML 更新 ExtJS 面板

javascript - 试图将我的 div 的高度设置为窗口高度的 80%