MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用
Posted
技术标签:
【中文标题】MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用【英文标题】:MongoDB Mongoose aggregate query deeply nested array remove empty results and populate references 【发布时间】:2020-03-29 11:38:38 【问题描述】:这个问题是previous question 的后续问题,我已经接受了答案。我有一个聚合查询,它根据日期范围返回深度嵌套的子文档数组的结果。查询返回指定日期范围内的正确结果,但它也会为与查询不匹配的结果返回一个空数组。
技术:MongoDB 3.6、Mongoose 5.5、NodeJS 12
问题 1: 有什么办法可以去掉查询不匹配的结果?
问题 2:
有没有办法在结果中“填充”Person db 引用?例如,要获取人员显示名称,我通常使用“填充”,例如 find().populate( path: 'Person', select: 'DisplayName')
记录架构
let RecordsSchema = new Schema(
RecordID:
type: Number,
index: true
,
RecordType:
type: String
,
Status:
type: String
,
// ItemReport array of subdocuments
ItemReport: [ItemReportSchema],
,
collection: 'records',
selectPopulatedPaths: false
);
let ItemReportSchema = new Schema(
// ObjectId reference
ReportBy:
type: Schema.Types.ObjectId,
ref: 'people'
,
ReportDate:
type: Date,
required: true
,
WorkDoneBy: [
Person:
type: Schema.Types.ObjectId,
ref: 'people'
,
CompletedHours:
type: Number,
required: true
,
DateCompleted:
type: Date
],
);
查询
有效,但也返回空结果,还需要填充 Person db 引用的 Display Name 属性
db.records.aggregate([
"$project":
"ItemReport":
$map:
input: "$ItemReport",
as: "ir",
in:
WorkDoneBy:
$filter:
input: "$$ir.WorkDoneBy",
as: "value",
cond:
"$and": [
"$ne": [ "$$value.DateCompleted", null ] ,
"$gt": [ "$$value.DateCompleted", new Date("2017-01-01T12:00:00.000Z") ] ,
"$lt": [ "$$value.DateCompleted", new Date("2018-12-31T12:00:00.000Z") ]
]
])
实际结果
"_id": "5dcb6406e63830b7aa5427ca",
"ItemReport": [
"WorkDoneBy": [
"_id": "5dcb6406e63830b7aa53d8ea",
"PersonID": 111,
"ReportID": 8855,
"CompletedHours": 3,
"DateCompleted": "2017-01-20T05:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fdba"
]
]
,
"_id": "5dcb6406e63830b7aa5427f1",
"ItemReport": [
"WorkDoneBy": [
"_id": "5dcb6406e63830b7aa53dcdc",
"PersonID": 4,
"ReportID": 9673,
"CompletedHours": 17,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fd69"
,
"_id": "5dcb6406e63830b7aa53dcdd",
"PersonID": 320,
"ReportID": 9673,
"CompletedHours": 3,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person": "5dcb6409e63830b7aa54fe88"
]
]
,
"_id": "5dcb6406e63830b7aa5427f2",
"ItemReport": [
"WorkDoneBy": []
]
,
"_id": "5dcb6406e63830b7aa5427f3",
"ItemReport": [
"WorkDoneBy": []
]
,
"_id": "5dcb6406e63830b7aa5427f4",
"ItemReport": [
"WorkDoneBy": []
]
,
"_id": "5dcb6406e63830b7aa5427f5",
"ItemReport": [
"WorkDoneBy": []
]
,
期望的结果
请注意,带有空“WorkDoneBy”数组的结果将被删除(问题 1),并填充“Person”显示名称(问题 2)。
"_id": "5dcb6406e63830b7aa5427f1",
"ItemReport": [
"WorkDoneBy": [
"_id": "5dcb6406e63830b7aa53dcdc",
"CompletedHours": 17,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person":
_id: "5dcb6409e63830b7aa54fe88",
DisplayName: "Joe Jones"
,
"_id": "5dcb6406e63830b7aa53dcdd",
"CompletedHours": 3,
"DateCompleted": "2017-05-18T04:00:00.000Z",
"Person":
_id: "5dcb6409e63830b7aa54fe88",
DisplayName: "Alice Smith"
]
]
,
【问题讨论】:
【参考方案1】:第一个问题相对容易回答,并且有多种方法可以做到这一点。我更喜欢将$anyElementTrue 与$map 一起使用,因为这些运算符是不言自明的。
"$match":
$expr: $anyElementTrue: $map: input: "$ItemReport", in: $gt: [ $size: "$$this.WorkDoneBy" , 0 ]
MongoPlayground
第二部分有点复杂,但仍然可以。您需要运行 $lookup 而不是填充,以从其他集合中获取数据。问题是您的Person
值嵌套很深,因此您需要在使用$reduce 和$setUnion 之前准备一个id
值列表。获得数据后,您需要使用 $map
和 $mergeObjects 将嵌套对象与人员实体合并。
$addFields:
people:
$reduce:
input: "$ItemReport",
initialValue: [],
in: $setUnion: [ "$$value", "$$this.WorkDoneBy.Person" ]
,
$lookup:
from: "people",
localField: "peopleIds",
foreignField: "_id",
as: "people"
,
$project:
_id: 1,
ItemReport:
$map:
input: "$ItemReport",
as: "ir",
in:
WorkDoneBy:
$map:
input: "$$ir.WorkDoneBy",
as: "wdb",
in:
$mergeObjects: [
"$$wdb",
Person: $arrayElemAt: [ $filter: input: "$people", cond: $eq: [ "$$this._id", "$$wdb.Person" ] , 0]
]
Complete Solution
【讨论】:
非常感谢您。我遇到的唯一问题是我还试图返回记录中的一些其他字段,例如 RecordID、RecordType 和 Status。我尝试将这些字段添加到底部的最后一个 $project 语句中,但它似乎没有返回这些字段。$project: RecordID: 1, RecordType: 1, Status: 1, ItemReport: $map: ...
@pengz 你应该将它们包含在$map
级别。第 ~ 9 行,然后 ~ 93 行,例如 RecordID: "$$ir.RecordID",
(我的 Playground 中的行号)
嗯,$$ir.RecordID
不起作用我想可能是因为 RecordID、RecordType 等属性不在项目报告级别,它们在上面的“***”级别。
哦,我想我明白了!我只需将 RecordID: 1、RecordType: 1 等添加到两个项目语句中。非常感谢你,这不是你第一次给我很大的帮助。 :) 例如"$project": "RecordID": 1, "RecordType": 1, "ItemReport": $map: ..
以上是关于MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用的主要内容,如果未能解决你的问题,请参考以下文章
如何从 1 分钟的嵌套数组数据中聚合 OHLC 5 分钟(mongodb、mongoose)
mongoDB,带有 $sum 和 $count 的 mongoose 聚合查询