Как добавить новое поле в агрегат mongodb из расчета любого из элементов массива - PullRequest
0 голосов
/ 24 августа 2018

Я изо всех сил пытаюсь выяснить, как добавить новое поле status в агрегат на основе расчета массива элементов.

В настоящее время я делаю это в Angular front-end, запрашивая обе коллекции и повторяя каждый элемент с помощью _.some() метода Lodash . Но я хочу перенести вычисления в бэкэнд, и я застрял с агрегатом MongoDB.

Ввод: Каждая подписка (по одной на пользователя) имеет много контрактов (по одному в месяц), и я хочу вычислить status подписки из своих контрактов.

[
  {
    "_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 могут не начинаться с« $ ».»)

Это мой шаг совокупности (тестирование только двух 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'
      ]}
    ]
  }
}

Мне удалось вычислить status из полей Подписки, но не из массива контрактов.

Я был бы признателен, если бы кто-нибудь мог указать мне правильное направление с агрегатной структурой, как другие примеры / вопросы, которые я нашел $sum / вычислить, но не добавлять новые поля.

Большое спасибо.

1 Ответ

0 голосов
/ 24 августа 2018

Я нашел способ: вместо прямого вычисления на шаге $addFields я делаю еще несколько шагов.

Пожалуйста, не стесняйтесь предлагать улучшения для агрегата, так как это мой первый большой агргейт:)

Шаг 1: $ match

Условия подписки, которые меня интересуют. (Используйте свои собственные)

Шаг 2: $ lookup

Присоединяйтесь к каждой подписке со всеми контрактами:

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

Шаг 3: $ раскрутить

Создайте один документ на каждый подписной контракт:

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

Шаг 4: $ sort

(мне нужно что-то особенное, используя последний / самый современный контракт, поэтому мне нужно их отсортировать)

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

Шаг 5: $ группа

Вот волшебство: добавьте новые поля с расчетом, используя все контракты на подписку (теперь каждый «контракт» находится в своем собственном документе, а не в массиве)

Мне нужно добавить “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

Здесь я рассчитываю другие статусы из данных подписки (не контрактов)

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

Шаг 7: $ project

И, наконец, я объединяю все статусы в одном “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'
                                    ]}
                                ]}
                            ]}
                        ]}
                    ]}
                ]}
              ]
            }
        }

TODO

Может быть, вы можете улучшить мой поток:

  • Вместо конечного объекта subscription и status можно ли переместить все данные из объекта subscription в корень (сопровождаемый вычисленным полем status)?
  • Видите ли вы другой лучший способ вычисления этого окончательного поля status, вместо $group и двух $project?

Спасибо за любые улучшения, которые вы можете предложить!

...