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 聚合查询深度嵌套数组删除空结果并填充引用的主要内容,如果未能解决你的问题,请参考以下文章

Mongoose - 查询深度嵌套的对象

如何从 1 分钟的嵌套数组数据中聚合 OHLC 5 分钟(mongodb、mongoose)

mongoDB,带有 $sum 和 $count 的 mongoose 聚合查询

mongoDB,带有 $sum 和 $count 的 mongoose 聚合查询

Mongodb 使用聚合框架过滤深度嵌套的数组

MongoDB/Mongoose - 与 geoNear 和子文档的聚合