【问题标题】:MongoDB Mongoose querying a deeply nested array of subdocuments by date rangeMongoDB Mongoose 按日期范围查询深度嵌套的子文档数组
【发布时间】:2020-03-28 15:46:36
【问题描述】:

我有一个问题是similar to this other question,但不完全相同,因为我的数据结构嵌套更深,接受的答案没有解决问题。

技术:MongoDB 3.6、Mongoose 5.5、NodeJS 12

我正在尝试查询深度嵌套的对象数组。该查询将接受来自用户的“开始日期”和“结束日期”。项目报告是一个子文档数组,其中包含另一个子文档数组“工作完成者”。在开始和结束日期范围内具有“CompletedDate”的所有 WorkDoneBy 对象应与其他几个属性一起返回。

所需的返回属性:

RecordID、RecordType、状态、ItemReport.WorkDoneBy.DateCompleted、ItemReport.WorkDoneBy.CompletedHours、ItemReport.WorkDoneBy.Person

记录架构:

let RecordsSchema = new Schema({
  RecordID: {
    type: Number,
    index: true
  },
  RecordType: {
    type: String,
    enum: ['Item', 'OSW']
  },
  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
    }
  }],
});

尝试 1:

db.records.aggregate([
    {
        "$match": {
            "ItemReport.WorkDoneBy.DateCompleted": { "$gt": new Date("2017-01-01T12:00:00.000Z"), "$lt": new Date("2018-12-31T12:00:00.000Z") }
        }
    },
    {
        "$project": {
            "ItemReport.WorkDoneBy": {
                "$filter": {
                    "input": "$ItemReport.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") ] }
                        ]
                    }
                }
            }
        }
    }
])

尝试 1 返回:

{ "_id" : ObjectId("5dcb6406e63830b7aa54269d"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa5426fb"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa542708"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }
{ "_id" : ObjectId("5dcb6406e63830b7aa542712"), "ItemReport" : [ { "WorkDoneBy" : [ ] } ] }

期望的回报(为简洁起见,删除了 _id):

请注意,WorkDoneBy 数组中的对象仅应在指定日期范围内返回。例如 RecordID 9018 ItemReport.WorkDoneBy 实际上有 2016 年的日期,但由于不在指定的日期范围内,所以没有返回。

{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 11, "DateCompleted" : ISODate("2017-09-29T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 36, "DateCompleted" : ISODate("2018-05-18T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 32, "DateCompleted" : ISODate("2018-05-18T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") } ] } ], "RecordID" : 9018, "RecordType" : "Item", "Status" : "Done" }
{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 1.5, "DateCompleted" : ISODate("2017-09-01T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fe5f") } ] } ], "RecordID" : 9019, "RecordType" : "Item", "Status" : "Done" }
{ "ItemReport" : [ { "WorkDoneBy" : [ { "CompletedHours" : 2, "DateCompleted" : ISODate("2017-09-08T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 18, "DateCompleted" : ISODate("2017-09-15T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") }, { "CompletedHours" : 7, "DateCompleted" : ISODate("2017-09-20T04:00:00Z"), "Person" : ObjectId("5dcb6409e63830b7aa54fd6e") } ] } ], "RecordID" : 9017, "RecordType" : "Item", "Status" : "Done" }

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query aggregation-framework


    【解决方案1】:

    请检查以下内容:

    db.collection.aggregate([
      {
        $project: {
          _id: 0,
          RecordID: 1,
          RecordType: 1,
          Status: 1,
          ItemReport: {
            $let: {
              vars: {
                wdb: {
                  $reduce: {
                    input: "$ItemReport.WorkDoneBy",
                    initialValue: [],
                    in: {
                      $concatArrays: [
                        "$$this",
                        "$$value"
                      ]
                    }
                  }
                }
              },
              in: {
                WorkDoneBy: {
                  $filter: {
                    input: "$$wdb",
                    as: "item",
                    cond: {
                      $and: [
                        {
                          $gte: [
                            "$$item.DateCompleted",
                            ISODate("2010-01-10T04:00:00Z") // start date
                          ]
                        },
                        {
                          $lte: [
                            "$$item.DateCompleted",
                            ISODate("2018-01-10T04:00:00Z") // end date
                          ]
                        },
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    ])
    

    mongodb playground

    【讨论】:

      【解决方案2】:

      这里的问题是WorkDoneBy 是一个嵌套在另一个数组(ItemReport)中的数组。因此单个$filter 是不够的,因为您需要迭代两次。您可以添加$map 来迭代外部数组:

      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") ] }
                                          ]
                                      }
                                  }
                              }
                          }
                      }
                  }
              }
          }
      ])
      

      【讨论】:

      • 谢谢!有什么方法可以删除与查询不匹配的结果?例如,有几个结果与查询不匹配,但返回一个空的 WorkDoneBy 数组。 { "_id": "5dcb6406e63830b7aa5427f6", "ItemReport": [ { "WorkDoneBy": [] } ] },
      • @pengz 是的,只需使用 $size 过滤掉具有空数组的那些。如果您需要示例,请告诉我(我可以在今天晚些时候提供一个,现在没时间了)
      • 再次感谢!是的,一个例子会有所帮助。还有一种方法可以“填充”“人”参考吗?我需要返回每个人的DisplayName 属性。我对这些改进有一个单独的问题,如果你想获得另一个“接受的答案”的分数:stackoverflow.com/questions/59179904/…
      猜你喜欢
      • 1970-01-01
      • 2018-04-05
      • 1970-01-01
      • 2012-10-17
      • 1970-01-01
      • 2013-08-12
      • 2017-06-23
      • 2016-02-15
      • 2020-03-22
      相关资源
      最近更新 更多