如何在 MongoDB 中聚合深度为 3 的文档?

How do you aggregate depth 3 documents in MongoDB?

我还不习惯mongoDB。

我想使用示例数据将它们制作成如下结果。 我想按天求每个字段的总和。

我翻了好几篇文献都找不到答案。

示例数据

[
  {
    _id: 1,
    parentId: 1,
    'stats': {
      'type': {
        'a': {
          'n:1': 2,
          'n:2': 2
        },
        'b': {
          'n:1': 2,
          'n:2': 1,
          'n:3': 1
        },
        'c': {
          'n:5': 4
        }
      }
    },
    time: ISODate('2021-10-12T05:00:00Z')
  },
  {
    _id: 2,
    parentId: 1,
    'stats': {
      'type': {
        'a': {
          'n:1': 1,
        },
        'b': {
          'n:1': 2,
          'n:2': 3,
          'n:3': 4
        },
        'c': {
          'n:4': 2
        }
      }
    },
    time: ISODate('2021-10-12T06:00:00Z')
  },
  {
    _id: 3,
    parentId: 2,
    'stats': {
      'type': {
        'a': {
          'n:1': 3,
        },
        'b': {
          'n:2': 5,
          'n:3': 7
        },
        'c': {
          'n:1': 1,
          'n:5': 2
        }
      }
    },
    time: ISODate('2021-10-13T05:00:00Z')
  }
]

结果

[
  { parentId: 1, 'n:1':7, 'n:2':6, 'n:3':5, 'n:4':2, 'n:5':4, year: '2021', month: '10', day:'12'},
  { parentId: 2, 'n:1':4, 'n:2':5, 'n:3':7, 'n:5':2, year: '2021', month: '10', day:'13'},
]

要按parentId、字段、年月日分组。 多个字段计数让我很辛苦。

请帮帮我。

你可以通过几种不同的方式来做到这一点,因为这里的主要需要是重组数据,这样我们就可以正确地$group它,这是我认为使用各种运算符最简单的方法.

我要提到的是,如果您使用的是版本 5+,则可以简化语法,因为他们添加了新的 $getField 运算符,允许您访问对象上的特定键。然而,由于这个版本仍然很新,我选择不使用它作为我的解决方案:

db.collection.aggregate([
  {
    $addFields: {
      values: {
        $reduce: {
          input: {
            $map: {
              input: {
                "$objectToArray": "$stats.type"
              },
              as: "typeObj",
              in: {
                "$objectToArray": "$$typeObj.v"
              },
              
            }
          },
          initialValue: [],
          in: {
            "$concatArrays": [
              "$$this",
              "$$value"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$values"
  },
  {
    $group: {
      _id: {
        parentId: "$parentId",
        key: "$values.k"
      },
      value: {
        $sum: "$values.v"
      },
      time: {
        $first: "$time"
      }
    }
  },
  {
    $group: {
      _id: "$_id.parentId",
      values: {
        $push: {
          k: "$_id.key",
          v: "$value"
        }
      },
      time: {
        $first: "$time"
      }
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        "$mergeObjects": [
          {
            parentId: "$_id"
          },
          {
            "$arrayToObject": "$values"
          },
          {
            year: {
              $year: "$time"
            }
          },
          {
            month: {
              $month: "$time"
            }
          },
          {
            day: {
              "$dayOfMonth": "$time"
            }
          },
          
        ]
      }
    }
  }
])

Mongo Playground

我的建议是这个。我使用 { $dateTrunc: { date: "$time", unit: "day" } } 而不是 time: { $first: "$time" },它需要 MongoDB 版本 5.0.

db.collection.aggregate([
   { $set: { stats: { $objectToArray: "$stats.type" } } },
   { $set: { stats: { $map: { input: "$stats.v", in: { $objectToArray: "$$this" } } } } },
   {
      $set: {
         stats: {
            $reduce: {
               input: "$stats",
               initialValue: [],
               in: { $concatArrays: ["$$value", "$$this"] }
            }
         }
      }
   },
   { $unwind: "$stats" },
   {
      $group: {
         _id: {
            parentId: "$parentId",
            day: { $dateTrunc: { date: "$time", unit: "day" } },
            k: "$stats.k"
         }, v: { $sum: "$stats.v" }
      }
   },
   {
      $group: {
         _id: {
            parentId: "$_id.parentId",
            day: "$_id.day"
         },
         stats: { $push: { k: "$_id.k", v: "$v" } }
      }
   },
   { $set: { stats: { $arrayToObject: "$stats" } } },
   { $set: { "_id.day": { $dateToParts: { date: "$_id.day" } } } },
   { $replaceRoot: { newRoot: { $mergeObjects: ["$_id", "$stats"] } } },
   { $set: { year: "$day.year", month: "$day.month", day: "$day.day" } }
])

部分

   { $set: { stats: { $objectToArray: "$stats.type" } } },
   { $set: { stats: { $map: { input: "$stats.v", in: { $objectToArray: "$$this" } } } } },
   {
      $set: {
         stats: {
            $reduce: {
               input: "$stats",
               initialValue: [],
               in: { $concatArrays: ["$$value", "$$this"] }
            }
         }
      }
   },

等于@TomSlabbaert 回答:

  {
    $addFields: {
      values: {
        $reduce: {
          input: {
            $map: {
              input: {
                "$objectToArray": "$stats.type"
              },
              as: "typeObj",
              in: {
                "$objectToArray": "$$typeObj.v"
              },
              
            }
          },
          initialValue: [],
          in: {
            "$concatArrays": [
              "$$this",
              "$$value"
            ]
          }
        }
      }
    }
  },

其余只是外观上的差异。

Mongo Playground

如果你不是运行 MongoDB 5.0 替换

day: { $dateTrunc: { date: "$time", unit: "day" } }

来自

year: { $year: "$time" }, 
month: { $month: "$time" }, 
day: { $dayOfMonth: "$time" } 

并用 $dateFromParts()

转换回 Date