【问题标题】:Aggregate and reduce a nested array based upon an ObjectId基于 ObjectId 聚合和减少嵌套数组
【发布时间】:2020-02-07 05:02:07
【问题描述】:

我有一个类似这样结构的 Event 文档,我正在尝试查询 employeeResponses 数组以收集单个员工的所有响应(可能存在也可能不存在):

[
  { 
    ...
    eventDate: 2019-10-08T03:30:15.000+00:00,
    employeeResponses: [
      {
        _id:"5d978d372f263f41cc624727", 
        response: "Available to work.", 
        notes: ""
      },
      ...etc
    ];
  }
];

我目前的猫鼬聚合是:

const eventResponses = await Event.aggregate([
  {
    // find all events for a selected month
    $match: {
      eventDate: {
        $gte: startOfMonth,
        $lte: endOfMonth,
      },
    },
  },
  {
    // unwind the employeeResponses array
    $unwind: {
      path: "$employeeResponses",
      preserveNullAndEmptyArrays: true,
    },
  },
  {
    $group: {
      _id: null,
      responses: {
        $push: {
          // if a response id matches the employee's id, then 
          // include their response; otherwise, it's a "No response."
          $cond: [
            { $eq: ["$employeeResponses._id", existingMember._id] },
            "$employeeResponses.response",
            "No response.",
          ],
        },
      },
    },
  },
  { $project: { _id: 0, responses: 1 } },
]);

您无疑会注意到,在超过 1 名员工记录回复后,上述查询将不起作用,因为它将每个单独的回复视为 T/F 条件,而不是所有回复employeeResponses 数组中作为单个 T/F 条件。

因此,我删除了初始 $match 之后的所有后续查询并进行了手动减少:

const responses = eventResponses.reduce((acc, { employeeResponses }) => {
  const foundResponse = employeeResponses.find(response => response._id.equals(existingMember._id));

  return [...acc, foundResponse ? foundResponse.response : "No response."];
}, []);

我想知道是否有可能实现上述相同的减少结果,但也许使用 mongo 的 $reduce 函数?或者重构上面的聚合查询以将employeeResponses 中的所有响应视为单个 T/F 条件?

此聚合的最终目标是从当前月份内找到的每个 Event 中提取任何先前记录的员工的回复和/或缺少回复,并将他们的回复放入单个数组中:

["I want to work.", "Available to work.", "Not available to work.", "No response.", "No response." ...etc]

【问题讨论】:

    标签: mongodb mongoose aggregation-framework


    【解决方案1】:

    您可以使用$filter$map 来重塑您的数据并按_id 过滤。如果数组为空,您可以继续使用$push$ifNull 来提供默认值:

    db.collection.aggregate([
        {
            $addFields: {
                employeeResponses: {
                    $map: {
                        input: {
                            $filter: {
                                input: "$employeeResponses",
                                cond: {
                                    $eq: [ "$$this._id", "5d978d372f263f41cc624727"]
                                }
                            }
                        },
                        in: "$$this.response"
                    }
                }
            }
        },
        {
            $group: {
                _id: null,
                responses: { $push: { $ifNull: [ { $arrayElemAt: [ "$employeeResponses", 0 ] }, "No response" ] } }
            }
        }
    ])
    

    Mongo Playground

    【讨论】:

    • 太棒了!感谢您花时间回答并提供一个工作示例——就像一个魅力!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    • 1970-01-01
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 2020-11-09
    • 2021-08-21
    相关资源
    最近更新 更多