MongoDB: получить сумму по годам / месяцам с вложенными значениями

#mongodb #sum #aggregate #nested-object

#mongodb #сумма #агрегировать #вложенный объект

Вопрос:

Я пытаюсь суммировать (расходы по месяцам / годам) коллекции с вложенными суммами — безуспешно.

Это коллекция (извлечение):

 [
{
    "_id" : ObjectId("5faaf88d0657287993e541a5"),
    "segment" : {
        "l1" : "Segment A",
        "l2" : "001"
    },
    "invoiceNo" : "2020.10283940",
    "invoicePos" : 3,
    "date" : ISODate("2019-09-06T00:00:00.000Z"),
    "amount" : {
        "document" : {
            "amount" : NumberDecimal("125.000000000000"),
            "currCode" : "USD"
        },
        "local" : {
            "amount" : NumberDecimal("123.800000000000"),
            "currCode" : "CHF"
        },
        "global" : {
            "amount" : NumberDecimal("123.800000000000"),
            "currCode" : "CHF"
        }
    }
},
...
]
  

Я хотел бы суммировать совокупный объем счетов-фактур за месяц в «глобальной» валюте.

Я попробовал этот запрос на MongoDB:

 db.invoices.aggregate( 
       {$project : { 
              month : {$month : "$date"}, 
              year : {$year :  "$date"},
              amount : 1
          }},
        {$unwind: '$amount'}, 
        {$group : { 
                _id : {month : "$month" ,year : "$year" },  
              total : {$sum : "$amount.global.amount"} 
        }})
  

В результате я получаю следующее:

 /* 1 */
{
    "_id" : ObjectId("5faaf88d0657287993e541a5"),
    "amount" : {
        "document" : {
            "amount" : NumberDecimal("125.000000000000"),
            "currCode" : "USD"
        },
        "local" : {
            "amount" : NumberDecimal("123.800000000000"),
            "currCode" : "CHF"
        },
        "global" : {
            "amount" : NumberDecimal("123.800000000000"),
            "currCode" : "CHF"
        }
    },
    "month" : 9,
    "year" : 2019
}

/* 2 */
{
    "_id" : ObjectId("5faaf88d0657287993e541ac"),
    "amount" : {
        "document" : {
            "amount" : NumberDecimal("105.560000000000"),
            "currCode" : "CHF"
        },
        "local" : {
            "amount" : NumberDecimal("105.560000000000"),
            "currCode" : "CHF"
        },
        "global" : {
            "amount" : NumberDecimal("105.560000000000"),
            "currCode" : "CHF"
        }
    },
    "month" : 11,
    "year" : 2020
}
  

Однако это не суммирует все счета-фактуры за месяц, а выглядит как отдельные строки счета-фактуры — без агрегирования.

Я хотел бы получить такой результат:

 [
  {
    "month": 11,
    "year": 2020,
    "amount" : NumberDecimal("99999.99") 
  },
  {
    "month": 10,
    "year": 2020,
    "amount" : NumberDecimal("99999.99") 
  },
  {
    "month": 9,
    "year": 2020,
    "amount" : NumberDecimal("99999.99") 
  }
]
  

Что не так с моим запросом?

Ответ №1:

Будет ли это полезно?

 db.invoices.aggregate([
  {
    $group: {
      _id: {
        month: {
          $month: "$date"
        },
        year: {
          $year: "$date"
        }
      },
      total: {
        $sum: "$amount.global.amount"
      }
    }
  },
  {$sort:{"_id.year":-1, "_id.month":-1}}
])
  

Игровая площадка

Если вам нужно какое-либо дополнительное объяснение, дайте мне знать, но код довольно короткий и понятный.

Комментарии:

1. Отлично, это работает! Однако при более чем 500 тыс. записей этот метод не выполняет задачу в течение 30 секунд. Есть ли другой способ выполнить запрос?

2. Вы можете попробовать {$limit:<number>} в качестве этапа после $sort затем, после того, как он найдет лучшие документы, он перестанет искать больше @Herdy

Ответ №2:

В принципе, ваш конвейер агрегации в порядке, есть несколько ошибок:

  • Конвейер агрегации ожидает массив
  • $unwind бесполезно, потому $amount что не является массивом. Один элемент в -> один документ
  • Вы можете использовать функцию даты напрямую

Итак, коротко и просто:

 db.invoices.aggregate([
  {
    $group: {
      _id: { month: { $month: "$date" }, year: { $year: "$date" } },
      total: { $sum: "$amount.global.amount" }
    }
  }
])