聚合组和嵌套值的总和

Aggregation with group and sum of nested values

我正在使用 Mongo,我需要聚合以下时间序列,按 account_id 分组并获取每个嵌套值的总和。为了举例,我只使用 pub 对象保持数据集简单,但在我的真实集合中,我还有其他对象和值要聚合

[
  {
    "account_id": 1,
    "pub": {
      "cpm": NumberDecimal("1"),
      "monthly": NumberDecimal("1.5")
    },
    "time": ISODate("2022-01-01T01:00:00.000"),
  },
  {
    "account_id": 2,
    "pub": {
      "cpm": NumberDecimal("3"),
      "monthly": NumberDecimal("3.5")
    },
    "time": ISODate("2022-01-01T01:00:00.000"),
  },
  {
    "account_id": 1,
    "pub": {
      "cpm": NumberDecimal("2"),
      "monthly": NumberDecimal("2.5")
    },
    "time": ISODate("2022-01-01T02:00:00.000"),
  },
  {
    "account_id": 2,
    "pub": {
      "cpm": NumberDecimal("4"),
      "monthly": NumberDecimal("4.5")
    },
    "time": ISODate("2022-01-01T02:00:00.000"),
  }
]

预期输出

[
  {
     "_id": 1, // account_id
     "pub": {
        "cpm": 3,
        "monthly": 4
     }
  },
  {
     "_id": 2, // account_id
     "pub": {
        "cpm": 7,
        "monthly": 8
     }
  }
]

我发现以下两种方法可以按预期工作,但对我来说它们似乎非常冗长,尤其是第一种。 考虑到真实集合中还有很多其他对象和值

方法一

db.collection.aggregate([
  {
    $group: {
      _id: '$account_id',
      pub: {
        $accumulator: {
          init: function () {
            return {
              cpm: 0,
              monthly: 0,
            };
          },
          accumulate: function (state, cpm, monthly) {
            return {
              cpm: state.cpm + cpm,
              monthly: state.monthly + monthly,
            };
          },
          accumulateArgs: [
            { $toDouble: '$pub.cpm' },
            { $toDouble: '$pub.monthly' },
          ],
          merge: function (state1, state2) {
            return {
              cpm: state1.cpm + state2.cpm,
              monthly: state1.monthly + state2.monthly,
            };
          },
          finalize: function (state) {
            return {
              cpm: state.cpm,
              monthly: state.monthly,
            };
          },
          lang: 'js',
        },
      },
    },
  }
])

方法二

db.collection.aggregate([
 {
    "$group": {
      "_id": "$account_id",
      "pub__cpm": {
        $sum: "$pub.cpm"
      },
      "pub__monthly": {
        $sum: "$pub.monthly"
      }
    }
  },
  {
    $set: {
      pub: {
        cpm: {
          "$toDouble": "$pub__cpm"
        },
        monthly: {
          "$toDouble": "$pub__monthly"
        }
      },
    },
  },
  {
    $unset: [
      "pub__cpm",
      "pub__monthly"
    ]
  }
)]

这样的东西会很棒

{
  "$group": {
    "_id": "$account_id",
    pub: {
      cpm: { $sum: "$pub.cpm" },
      monthly: { $sum: "$pub.monthly" },
    },
  }
}

但它抛出“字段“$pub”必须是一个累加器对象”,这就是我最终使用第一种方法的原因。

有更好的方法可以达到同样的效果吗?如果不是,哪种方法更快? 谢谢

您可以使用 $project 来格式化您的输出,而不是 $set$unset 使用 $project 像这样

mongoplayground

db.collection.aggregate([
  {
    "$group": {
      "_id": "$account_id",
      "pub__cpm": { $sum: "$pub.cpm" },
      "pub__monthly": { $sum: "$pub.monthly" }
    }
  },
  {
    "$project": {
      "pub": {
        "cpm": "$pub__cpm",
        "monthly": "$pub__monthly"
      }
    }
  }
])

小组赛结束后,您可以使用 project 操作将变量 cpm 和月度的累计总和投影到 pub 中。

db.collection.aggregate([
  {
    "$group": {
      "_id": "$account_id",
      cpm: {
        $sum: "$pub.cpm"
      },
      monthly: {
        $sum: "$pub.monthly"
      },
      
    }
  },
  {
    "$project": {
      pub: {
        cpm: "$cpm",
        monthly: "$monthly"
      }
    }
  }
])