在MongoDB深层嵌套对象数组中实现Lookup关联查询
MongoDB聚合查询:为嵌套评论关联用户详情
已实现工单聚合查询中ticketLogs相关字段的关联查询(用户、状态、部门等),现需为每个ticketLog下comments数组中comment对象的commentedBy字段关联用户详情,展示评论者信息。
当前查询代码
const result = await this.ticketsv2Model .aggregate([ { $match: query, }, { $sort: { createdAt: -1, }, }, ...generateLookupStage('issues', 'issueId', ['_id', 'name']), ...generateLookupStage('subissues', 'subIssueId', ['_id', 'name']), ...generateLookupStage('tickettypes', 'typeId', ['_id', 'name']), ...generateLookupStage('statustypes', 'statusId', ['_id', 'name']), ...generateLookupStage('departments', 'assignedDepartment', [ '_id', 'name', ]), ...generateLookupStage('users', 'assignedTo', [ '_id', 'extension', 'firstName', 'lastName', ]), ...generateLookupStage('users', 'openedBy', [ '_id', 'extension', 'firstName', 'lastName', ]), { $unwind: '$ticketLogs', }, ...generateLookupStage('users', 'ticketLogs.userId', [ '_id', 'firstName', 'lastName', 'extension', ]), ...generateLookupStage('statustypes', 'ticketLogs.statusId', [ '_id', 'name', ]), ...generateLookupStage( 'departments', 'ticketLogs.assignedDepartment', ['_id', 'name', 'number'], ), ...generateLookupStage('users', 'ticketLogs.assignedTo', [ '_id', 'firstName', 'lastName', 'extension', ]), { $group: { _id: '$_id', issueId: { $first: '$issueId' }, subIssueId: { $first: '$subIssueId' }, typeId: { $first: '$typeId' }, ticketId: { $first: '$ticketId' }, resolutionTime: { $first: '$resolutionTime' }, clientInfo: { $first: '$clientInfo' }, statusId: { $first: '$statusId' }, openedBy: { $first: '$openedBy' }, remarks: { $first: '$remarks' }, assignedTo: { $first: '$assignedTo' }, assignedDepartment: { $first: '$assignedDepartment' }, ticketLogs: { $push: { chatId: '$ticketLogs.chatId', recordingId: '$ticketLogs.recordingId', voiceMailRecordingId: '$ticketLogs.voiceMailRecordingId', remarks: '$ticketLogs.remarks', statusId: '$ticketLogs.statusId', userId: '$ticketLogs.userId', assignedTo: '$ticketLogs.assignedTo', assignedDepartment: '$ticketLogs.assignedDepartment', comments: '$ticketLogs.comments', }, }, }, }, { $project: { createdAt: 1, issueId: 1, subIssueId: 1, typeId: 1, clientInfo: 1, statusId: 1, remarks: 1, assignedDepartment: 1, assignedTo: 1, ticketLogs: 1, ticketId: 1, openedBy: 1, _id: 1, action: { $switch: { branches: [ { case: { $eq: ['$assignedTo._id', new Types.ObjectId(user)], }, then: 'update', }, { case: { $and: [ { $in: ['$assignedDepartment._id', userDepartments], }, { $eq: [{ $type: '$assignedTo._id' }, 'missing'] }, ], }, then: 'pick', }, ], default: 'view', }, }, }, }, ]) .exec();
自定义函数说明
generateLookupStage是自定义聚合阶段生成函数,参数依次为:
- 关联集合名称
- 本地关联字段路径
- 需要返回的关联文档字段列表
示例工单文档
{ "_id": { "$oid": "65b2143e5b239b78bc756a68" }, "issueId": { "$oid": "644e940200d3bd6ab311f1fb" }, "subIssueId": { "$oid": "644e94b100d3bd6ab311f216" }, "typeId": { "$oid": "65828ebea1726254da22e506" }, "clientInfo": { "clientId": "1231235645123", "clientEmail": "arpan@arpan.com", "clientPhone": "12233", "clientName": "Arpan Dhakal", "clientImage": "linkto.image", "_id": { "$oid": "65b2143e5b239b78bc756a69" } }, "statusId": { "$oid": "658294ca9777467f6777802e" }, "remarks": "Custom remarks here", "assignedDepartment": { "$oid": "658294a2aa8146af8fe85b50" }, "ticketLogs": [ { "chatId": "658294a2aa8146af8fe85b50", "recordingId": "658294a2aa8146af8fe85b50", "voiceMailRecordingId": "658294a2aa8146af8fe85b50", "remarks": "Ticket Created", "statusId": { "$oid": "658294f1660cac3e3acda29a" }, "userId": { "$oid": "65af5d2e08a356fd82718d64" }, "assignedTo": null, "assignedDepartment": { "$oid": "658294a2aa8146af8fe85b50" }, "_id": { "$oid": "65b2143e5b239b78bc756a6a" }, "createdAt": { "$date": "2024-01-25T07:56:46.778Z" }, "updatedAt": { "$date": "2024-01-25T07:56:46.778Z" }, "comments": [ { "comment": "hello world", "createdAt": { "$date": "2024-01-26T06:21:21.429Z" }, "commentedBy": { "$oid": "65af5d2e08a356fd82718d64" }, "_id": { "$oid": "65b34f61ec6cb747fc19f73a" } }, { "comment": "hello world", "createdAt": { "$date": "2024-01-26T06:30:14.002Z" }, "commentedBy": { "$oid": "65af5d2e08a356fd82718d64" }, "_id": { "$oid": "65b35176cb2f23c9563c45f5" } }, { "comment": "comment", "commentedBy": { "$oid": "644e06fc00d3bd6ab311d5fd" }, "_id": { "$oid": "65e99131c599c2d633062497" }, "createdAt": { "$date": "2024-03-07T10:04:33.007Z" }, "updatedAt": { "$date": "2024-03-07T10:04:33.007Z" } } ] } ], "ticketId": "T-55NNXUBW", "openedBy": { "$oid": "644e06fc00d3bd6ab311d5fd" }, "createdAt": { "$date": "2024-01-25T07:56:46.778Z" }, "updatedAt": { "$date": "2024-03-07T10:04:33.007Z" }, "__v": 0, "assignedTo": { "$oid": "644e06fc00d3bd6ab311d5fd" } }
解决方案
要实现评论者信息关联,需在$unwind: '$ticketLogs'之后、原$group之前,添加展开评论、关联用户、重组数组的步骤,最终恢复原文档结构。
修改后的完整查询代码
const result = await this.ticketsv2Model .aggregate([ { $match: query, }, { $sort: { createdAt: -1, }, }, ...generateLookupStage('issues', 'issueId', ['_id', 'name']), ...generateLookupStage('subissues', 'subIssueId', ['_id', 'name']), ...generateLookupStage('tickettypes', 'typeId', ['_id', 'name']), ...generateLookupStage('statustypes', 'statusId', ['_id', 'name']), ...generateLookupStage('departments', 'assignedDepartment', [ '_id', 'name', ]), ...generateLookupStage('users', 'assignedTo', [ '_id', 'extension', 'firstName', 'lastName', ]), ...generateLookupStage('users', 'openedBy', [ '_id', 'extension', 'firstName', 'lastName', ]), { $unwind: '$ticketLogs', }, ...generateLookupStage('users', 'ticketLogs.userId', [ '_id', 'firstName', 'lastName', 'extension', ]), ...generateLookupStage('statustypes', 'ticketLogs.statusId', [ '_id', 'name', ]), ...generateLookupStage( 'departments', 'ticketLogs.assignedDepartment', ['_id', 'name', 'number'], ), ...generateLookupStage('users', 'ticketLogs.assignedTo', [ '_id', 'firstName', 'lastName', 'extension', ]), // 新增:展开comments数组,保留无评论的日志 { $unwind: { path: '$ticketLogs.comments', preserveNullAndEmptyArrays: true } }, // 新增:关联评论者用户信息 ...generateLookupStage('users', 'ticketLogs.comments.commentedBy', [ '_id', 'firstName', 'lastName', 'extension' ]), // 新增:替换commentedBy为关联的用户详情 { $addFields: { 'ticketLogs.comments.commentedBy': { $ifNull: ['$ticketLogs.comments.commentedBy', null] } } }, // 新增:重组comments数组到对应ticketLog { $group: { _id: { ticketId: '$_id', ticketLogId: '$ticketLogs._id' }, issueId: { $first: '$issueId' }, subIssueId: { $first: '$subIssueId' }, typeId: { $first: '$typeId' }, ticketId: { $first: '$ticketId' }, resolutionTime: { $first: '$resolutionTime' }, clientInfo: { $first: '$clientInfo' }, statusId: { $first: '$statusId' }, openedBy: { $first: '$openedBy' }, remarks: { $first: '$remarks' }, assignedTo: { $first: '$assignedTo' }, assignedDepartment: { $first: '$assignedDepartment' }, ticketLog: { $first: { chatId: '$ticketLogs.chatId', recordingId: '$ticketLogs.recordingId', voiceMailRecordingId: '$ticketLogs.voiceMailRecordingId', remarks: '$ticketLogs.remarks', statusId: '$ticketLogs.statusId', userId: '$ticketLogs.userId', assignedTo: '$ticketLogs.assignedTo', assignedDepartment: '$ticketLogs.assignedDepartment', _id: '$ticketLogs._id', createdAt: '$ticketLogs.createdAt', updatedAt: '$ticketLogs.updatedAt' } }, comments: { $push: '$ticketLogs.comments' } } }, // 新增:将comments合并到ticketLog { $addFields: { 'ticketLog.comments': '$comments' } }, // 新增:重组ticketLogs数组到原工单 { $group: { _id: '$_id.ticketId', issueId: { $first: '$issueId' }, subIssueId: { $first: '$subIssueId' }, typeId: { $first: '$typeId' }, ticketId: { $first: '$ticketId' }, resolutionTime: { $first: '$resolutionTime' }, clientInfo: { $first: '$clientInfo' }, statusId: { $first: '$statusId' }, openedBy: { $first: '$openedBy' }, remarks: { $first: '$remarks' }, assignedTo: { $first: '$assignedTo' }, assignedDepartment: { $first: '$assignedDepartment' }, ticketLogs: { $push: '$ticketLog' } } }, { $project: { createdAt: 1, issueId: 1, subIssueId: 1, typeId: 1, clientInfo: 1, statusId: 1, remarks: 1, assignedDepartment: 1, assignedTo: 1, ticketLogs: 1, ticketId: 1, openedBy: 1, _id: 1, action: { $switch: { branches: [ { case: { $eq: ['$assignedTo._id', new Types.ObjectId(user)], }, then: 'update', }, { case: { $and: [ { $in: ['$assignedDepartment._id', userDepartments], }, { $eq: [{ $type: '$assignedTo._id' }, 'missing'] }, ], }, then: 'pick', }, ], default: 'view', }, }, }, }, ]) .exec();
关键修改说明
- 用
$unwind展开ticketLogs.comments,通过preserveNullAndEmptyArrays保留无评论的日志条目 - 调用
generateLookupStage关联users集合,获取评论者的详细信息 - 通过两次
$group操作分别重组comments
相关产品推荐
相关产品推荐

