嵌套过滤器:$filter 数组,然后是 $filter 子数组

Posted

技术标签:

【中文标题】嵌套过滤器:$filter 数组,然后是 $filter 子数组【英文标题】:Nested filters: $filter array, then $filter child array 【发布时间】:2016-11-14 00:39:34 【问题描述】:

本质上,我正在尝试过滤掉已“丢弃”的子文档和子子文档。这是我的架构的精简版:

permitSchema = 
  _id,
  name,
  ...
  feeClassifications: [
    new Schema(
      _id,
      _trashed,
      name,
      fees: [
        new Schema(
          _id,
          _trashed,
          name,
          amount
        )
      ]
    )
  ],
  ...

所以我可以通过feeClassifications 获得我想要的效果。但我正在努力寻找一种对feeClassifications.fees 也产生相同效果的方法。

所以,这可以按需要工作:

Permit.aggregate([
   $match:  _id: mongoose.Types.ObjectId(req.params.id) ,
   $project: 
    _id: 1,
    _name: 1,
    feeClassifications: 
      $filter: 
        input: '$feeClassifications',
        as: 'item',
        cond:  $not: $gt: ['$$item._trashed', null]  
      
    
  
])

但我也想过滤嵌套数组fees。我尝试了一些方法,包括:

Permit.aggregate([
   $match:  _id: mongoose.Types.ObjectId(req.params.id) ,
   $project: 
    _id: 1,
    _name: 1,
    feeClassifications: 
      $filter: 
        input: '$feeClassifications',
        as: 'item',
        cond:  $not: $gt: ['$$item._trashed', null]  
      ,
      fees: 
        $filter: 
          input: '$fees',
          as: 'fee',
          cond:  $not: $gt: ['$$fee._trashed', null]  
        
      
    
  
])

这似乎跟 mongodb docs 最接近。但我得到了错误: this object is already an operator expression, and can't be used as a document expression (at 'fees')

更新:------------

根据要求,这是一个示例文档:


    "_id" : ObjectId("57803fcd982971e403e3e879"),
    "_updated" : ISODate("2016-07-11T19:24:27.204Z"),
    "_created" : ISODate("2016-07-09T00:05:33.274Z"),
    "name" : "Single Event",
    "feeClassifications" : [ 
        
            "_updated" : ISODate("2016-07-11T19:05:52.418Z"),
            "_created" : ISODate("2016-07-11T17:49:12.247Z"),
            "name" : "Event Type 1",
            "_id" : ObjectId("5783dc18e09be99840fad29f"),
            "fees" : [ 
                
                    "_updated" : ISODate("2016-07-11T18:51:10.259Z"),
                    "_created" : ISODate("2016-07-11T18:41:16.110Z"),
                    "name" : "Basic Fee",
                    "amount" : 156.5,
                    "_id" : ObjectId("5783e84cc46a883349bb2339")
                , 
                
                    "_updated" : ISODate("2016-07-11T19:05:52.419Z"),
                    "_created" : ISODate("2016-07-11T19:05:47.340Z"),
                    "name" : "Secondary Fee",
                    "amount" : 50,
                    "_id" : ObjectId("5783ee0bad7bf8774f6f9b5f"),
                    "_trashed" : ISODate("2016-07-11T19:05:52.410Z")
                
            ]
        , 
        
            "_updated" : ISODate("2016-07-11T18:22:21.567Z"),
            "_created" : ISODate("2016-07-11T18:22:21.567Z"),
            "name" : "Event Type 2",
            "_id" : ObjectId("5783e3dd540078de45bbbfaf"),
            "_trashed" : ISODate("2016-07-11T19:24:27.203Z")
        
    ]

这是所需的输出(“垃圾”子文档被排除在 feeClassificationsfees 之外):


    "_id" : ObjectId("57803fcd982971e403e3e879"),
    "_updated" : ISODate("2016-07-11T19:24:27.204Z"),
    "_created" : ISODate("2016-07-09T00:05:33.274Z"),
    "name" : "Single Event",
    "feeClassifications" : [ 
        
            "_updated" : ISODate("2016-07-11T19:05:52.418Z"),
            "_created" : ISODate("2016-07-11T17:49:12.247Z"),
            "name" : "Event Type 1",
            "_id" : ObjectId("5783dc18e09be99840fad29f"),
            "fees" : [ 
                
                    "_updated" : ISODate("2016-07-11T18:51:10.259Z"),
                    "_created" : ISODate("2016-07-11T18:41:16.110Z"),
                    "name" : "Basic Fee",
                    "amount" : 156.5,
                    "_id" : ObjectId("5783e84cc46a883349bb2339")
                
            ]
        
    ]

【问题讨论】:

【参考方案1】:

由于我们要过滤外部和内部数组字段,我们可以使用$map 变量运算符返回一个包含我们想要的“值”的数组。

$map 表达式中,我们提供了一个逻辑$conditional $filter 来从文档和子文档数组字段中删除不匹配的文档。

条件为$lt,当子文档和/或子文档数组字段中不存在字段“_trashed”时返回true。

请注意,在 $cond 表达式中,我们还为 <false case> 返回 false。当然,我们需要对$map结果应用过滤器以删除所有false

Permit.aggregate(
    [ 
         "$match":  "_id": mongoose.Types.ObjectId(req.params.id)  ,
         "$project":  
            "_updated": 1, 
            "_created": 1, 
            "name": 1, 
            "feeClassifications":  
                "$filter": 
                    "input": 
                        "$map":  
                            "input": "$feeClassifications", 
                            "as": "fclass", 
                            "in":  
                                "$cond": [ 
                                     "$lt": [ "$$fclass._trashed", 0 ] , 
                                     
                                        "_updated": "$$fclass._updated", 
                                        "_created": "$$fclass._created", 
                                        "name": "$$fclass.name", 
                                        "_id": "$$fclass._id", 
                                        "fees":  
                                            "$filter":  
                                                "input": "$$fclass.fees", 
                                                "as": "fees", 
                                                "cond":  "$lt": [ "$$fees._trashed", 0 ] 
                                            
                                        
                                    , 
                                    false 
                                ]
                            
                        
                    , 
                    "as": "cls",  
                    "cond": "$$cls"
                
            
        
    ]
)

在即将发布的 MongoDB 版本中(截至撰写本文时以及自 MongoDB 3.3.5 起),您可以将 $map 表达式中的 $cond 表达式替换为 $switch 表达式:

Permit.aggregate(
    [ 
         "$match":  "_id": mongoose.Types.ObjectId(req.params.id)  ,
         "$project":  
            "_updated": 1, 
            "_created": 1, 
            "name": 1, 
            "feeClassifications":  
                "$filter": 
                    "input": 
                        "$map":  
                            "input": "$feeClassifications", 
                            "as": "fclass", 
                            "in":  
                                "$switch":  
                                    "branches": [ 
                                         
                                            "case":  "$lt": [ "$$fclass._trashed", 0 ] , 
                                            "then":  
                                                "_updated": "$$fclass._updated", 
                                                "_created": "$$fclass._created", 
                                                "name": "$$fclass.name", 
                                                "_id": "$$fclass._id", 
                                                "fees":  
                                                    "$filter":  
                                                        "input": "$$fclass.fees", 
                                                        "as": "fees", 
                                                        "cond":  "$lt": [ "$$fees._trashed", 0 ] 
                                                    
                                                
                                             
                                         
                                    ], 
                                    "default":  false 
                                
                            
                        
                    ,
                    "as": "cls",  
                    "cond": "$$cls"
                
            
        
    ]
)

【讨论】:

感激不尽!感谢您提供信息丰富的答案和解释。这让我花在自己试图解决这个问题上的时间变得值得!【参考方案2】:

对于更复杂的大数据,这将是不必要的困难。 只需在 $filter 输入中添加一个虚线注释字段即可对其进行编辑。您可以通过虚线注释将文档搜索到任何深度的 JSON,而无需进一步复杂的 $filter 映射。

"$filter":
       "input": "$feeClassifications._trashed",
       "as": "trashed",
       "cond":  "$lt": [ "$$trashed._trashed", 0 ]                            
 

【讨论】:

以上是关于嵌套过滤器:$filter 数组,然后是 $filter 子数组的主要内容,如果未能解决你的问题,请参考以下文章

Linq 查询以返回具有特定属性值的嵌套数组

Spark Sql 查询嵌套记录。我想先过滤嵌套的记录数组,然后爆炸(将它们展开成行)

SpringBoot--Filter过滤器

PHP Filter 函数

Filter过滤器(Servlet)

过滤器-filter