【问题标题】:Reducing an array of values into object with their count using aggregation framework使用聚合框架将值数组减少为对象及其计数
【发布时间】:2019-12-30 10:02:17
【问题描述】:

我们正在使用 MongoDB 来记录统计信息。该方法是将对象的每个操作记录在其自己的文档中,然后按小时汇总它们并将它们存储在不同的集合中。示例文件如下:

[{
    "_id" : ObjectId("5e05de1e86029610dc2c6f9c"),
    "object_type" : 1,
    "object_id" : 1003,
    "browser" : "chrome",
    "os" : "osx",
    "device" : "android",
    "category" : 3,
    "country" : "gb",
    "action" : "impression",
    "date_added" : ISODate("2019-12-26T19:00:00.000Z")
},{
    "_id" : ObjectId("5e06226586029610db417b7a"),
    "object_type" : 1,
    "object_id" : 1003,
    "browser" : "firefox",
    "os" : "osx",
    "device" : "lg_tv",
    "category" : 1,
    "country" : "pe",
    "action" : "impression",
    "date_added" : ISODate("2019-12-25T19:00:00.000Z")
},{
    "_id" : ObjectId("5e06226586029610db417b7b"),
    "object_type" : 1,
    "object_id" : 1009,
    "browser" : "uc_browser",
    "os" : "osx",
    "device" : "android",
    "category" : 4,
    "country" : "ru",
    "action" : "view",
    "date_added" : ISODate("2019-12-25T19:00:00.000Z")
}]

输出应该是:

[{
    "object_id": 1003,
    "object_type": 1,
    "action": "impression",
    "total": 2,
    "date": "2019-12-26 19:00:00",
    "browsers": { "firefox": 1, "chrome": 1 },
    "systems": { "osx": 2 },
    "countries": { "gb": 1, "pe": 1 },
    "devices": { "android": 1, "lg_tv": 1 },
    "categories": { "3": 1, "1": 1 }
},
{
    "object_id": 1009,
    "object_type": 1,
    "action": "view",
    "total": 1,
    "date": "2019-12-26 19:00:00",
    "browsers": { "uc_browser": 1 },
    "systems": { "osx": 1 },
    "countries": { "ru": 1 },
    "devices": { "android": 1 },
    "categories": { "4": 1 }
}]

聚合管道:

[
  {
    "$match": {
      "date_added": {
        "$gte": {
          "$date": {
            "$numberLong": "1576820825000"
          }
        }
      }
    }
  },
  {
    "$group": {
      "_id": {
        "object_id": "$object_id",
        "object_type": "$object_type",
        "action": "$action",
        "date": {
          "$dateToString": {
            "format": "%Y-%m-%d %H-00-00",
            "date": "$date_added"
          }
        }
      },
      "total": {
        "$sum": 1
      },
      "countries": {
        "$push": "$country"
      }
    }
  },
  {
    "$project": {
      "action": "$_id.action",
      "object_id": "$_id.object_id",
      "object_type": "$_id.object_type",
      "date": "$_id.date",
      "total": 1,
      "countries": 1,
      "systems": 1,
      "devices": 1,
      "categories": 1,
      "_id": 0
    }
  },
  {
    "$sort": {
      "total": -1
    }
  }
]

此管道在给定时间为特定操作提供对象的总数,并将每个国家/地区推入 countries 数组 - 为了便于阅读,从 $group 中删除了其他索引。

我坚持将countries 数组转换为所需的对象。两个问题在我脑海中浮现。

  1. 是否可以使用单个聚合管道?
  2. 我应该只使用上述管道返回文档并使用脚本处理其余索引吗?

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    有可能,但有点乏味...

    你需要$group下一阶段的每个新字段并累积以前的字段。

    假设

    您对 "object_id": 1003total:2 的预期结果,但 date_added 是 2019-12-26 和 2019-12-25。所以,我把这两个文件都改成了 2019-12-26

    db.collection.aggregate([
      {
        "$match": {
          "date_added": {
            "$gte": {
              "$date": {
                "$numberLong": "1576820825000"
              }
            }
          }
        }
      },
      {
        $group: {
          _id: {
            "object_id": "$object_id",
            "object_type": "$object_type",
            "action": "$action",
            "date": {
              "$dateToString": {
                "format": "%Y-%m-%d %H-00-00",
                "date": "$date_added",
                timezone: "GMT"
              }
            }
          },
          data: {
            "$push": "$$ROOT"
          },
          total: {
            $sum: 1
          }
        }
      },
      {
        $unwind: "$data"
      },
      {
        $group: {
          _id: {
            _id: "$_id",
            "tmp": "$data.category"
          },
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          count: {
            $sum: 1
          }
        }
      },
      {
        $group: {
          _id: "$_id._id",
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          categories: {
            $push: {
              k: {
                $toString: "$_id.tmp"
              },
              v: "$count"
            }
          }
        }
      },
      {
        $unwind: "$data"
      },
      {
        $unwind: "$data"
      },
      {
        $group: {
          _id: {
            _id: "$_id",
            "tmp": "$data.device"
          },
          categories: {
            $first: "$categories"
          },
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          count: {
            $sum: 1
          }
        }
      },
      {
        $group: {
          _id: "$_id._id",
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          categories: {
            $first: "$categories"
          },
          devices: {
            $push: {
              k: "$_id.tmp",
              v: "$count"
            }
          }
        }
      },
      {
        $unwind: "$data"
      },
      {
        $unwind: "$data"
      },
      {
        $group: {
          _id: {
            _id: "$_id",
            "tmp": "$data.country"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          count: {
            $sum: 1
          }
        }
      },
      {
        $group: {
          _id: "$_id._id",
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          countries: {
            $push: {
              k: "$_id.tmp",
              v: "$count"
            }
          }
        }
      },
      {
        $unwind: "$data"
      },
      {
        $unwind: "$data"
      },
      {
        $group: {
          _id: {
            _id: "$_id",
            "tmp": "$data.os"
          },
          countries: {
            $first: "$countries"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          count: {
            $sum: 1
          }
        }
      },
      {
        $group: {
          _id: "$_id._id",
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          countries: {
            $first: "$countries"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          systems: {
            $push: {
              k: "$_id.tmp",
              v: "$count"
            }
          }
        }
      },
      {
        $unwind: "$data"
      },
      {
        $unwind: "$data"
      },
      {
        $group: {
          _id: {
            _id: "$_id",
            "tmp": "$data.browser"
          },
          systems: {
            $first: "$systems"
          },
          countries: {
            $first: "$countries"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          count: {
            $sum: 1
          }
        }
      },
      {
        $group: {
          _id: "$_id._id",
          data: {
            $push: "$data"
          },
          total: {
            $first: "$total"
          },
          systems: {
            $first: "$systems"
          },
          countries: {
            $first: "$countries"
          },
          devices: {
            $first: "$devices"
          },
          categories: {
            $first: "$categories"
          },
          browsers: {
            $push: {
              k: "$_id.tmp",
              v: "$count"
            }
          }
        }
      },
      {
        $project: {
          _id: 0,
          action: "$_id.action",
          date: "$_id.date",
          object_id: "$_id.object_id",
          object_type: "$_id.object_type",
          total: 1,
          categories: {
            $arrayToObject: "$categories"
          },
          countries: {
            $arrayToObject: "$countries"
          },
          devices: {
            $arrayToObject: "$devices"
          },
          systems: {
            $arrayToObject: "$systems"
          },
          browsers: {
            $arrayToObject: "$browsers"
          }
        }
      },
      {
        $sort: {
          object_id: 1,
          date: 1
        }
      }
    ])
    

    MongoPlayground

    注意:其他方法是使用$facet单独创建字段,然后将它们合并到最终对象中,但是MongoPlayground sometimes worked buggy(点击运行按钮几次,你会得到不同的结果)

    【讨论】:

    • 谢谢。 Playground 显示异常输出。我将在更大的数据集上尝试这两个管道。在性能方面,$facet 或多个$group 哪个更好? $facet 看起来更优雅。
    • 它具有相同的性能。 $facet 旨在在 1 步中组合不同的聚合。如果您更改预期结果,更改$facet 比更改$group 更容易。 注意:每个管道输出文档不能超过 16Mb,并使用allowDiskUse 选项启用聚合管道阶段以将数据写入临时文件。
    猜你喜欢
    • 1970-01-01
    • 2020-07-15
    • 2020-08-16
    • 2019-08-01
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    相关资源
    最近更新 更多