如何在MongoDB中查询数组中的字段超过n次的文档

Posted

技术标签:

【中文标题】如何在MongoDB中查询数组中的字段超过n次的文档【英文标题】:How to query for documents where array has a field more than n number of times in MongoDB 【发布时间】:2015-10-13 11:56:46 【问题描述】:

我有一个 MongoDB 集合 world,其中包含以下格式的文档:


  _id : ObjectId("4e8ae86d08101908e1000001"),
  country : [
      
          state: "Newyork",
          type: 1
      ,
      
          state: "California",
          type: 1
      ,
      
          state: "Texas",
          type: 2
      
  ]

我们可以很容易地得到数组中有四个或更多状态的文档:

db.world.find('country.4': $exists: true )

但是我怎样才能获得具有四个或更多type: 1 状态的国家/地区数组的文档?

另外,我想避免在查询中使用$where 运算符。

编辑 1

Blakes Seven 的回答对我来说似乎是正确的,但是当我尝试做相反的事情时,即;获取 less 的文档比 n 个字段,然后我得到错误的结果:

这里是查询:

db.world.aggregate([
     "$redact": 
        "$cond": 
            "if": 
                "$lte": [
                     "$size":  "$setDifference": [
                         "$map": 
                            "input": "$country",
                            "as": "el",
                            "in": 
                                "$cond": 
                                    "if":  "$eq": [ "$$el.type", 769 ] ,
                                    "then": "$$el",
                                    "else": false
                                
                            
                        ,
                        [false]
                    ],
                    4
                ]
            ,
            "then": "$$KEEP",
            "else": "$$PRUNE"
        
    
]);

【问题讨论】:

【参考方案1】:

底线是您需要过滤掉不匹配并“计数”匹配的出现,以确定文档是否满足您的条件。这可以通过过滤数组上的$size 运算符来完成,作为$redact 逻辑测试的一部分。

$setIsSubset 在别处提出的建议不起作用,因为“集合”基本上取消了任何重复的项目。这意味着任何匹配都会减少到:

"$setIsSubset": [[1,0],[1]]

这当然是false 条件。这是因为大多数情况下存在不匹配的数组成员(因此产生 0 )并且每个“集合”有效地减少为它的“唯一”成员。即使“所有”成员都匹配,结果也会缩小为:

"$setIsSubset": [[1],[1]]

虽然是肯定匹配,但这绝对不会断言实际满足所需的“数量”匹配。

因此,只要数组成员本身实际上是“唯一的”,那么您可以采用这种方法来过滤和计算匹配项:

db.world.aggregate([
     "$match":  "country.3":  "$exists": true  ,
     "$redact": 
        "$cond": 
            "if": 
                "$gte": [
                     "$size":  "$setDifference": [
                         "$map": 
                            "input": "$country",
                            "as": "el",
                            "in": 
                                "$cond": 
                                    "if":  "$eq": [ "$$el.type", 1 ] ,
                                    "then": "$$el",
                                    "else": false
                                
                            
                        ,
                        [false]
                    ],
                    4
                ]
            ,
            "then": "$$KEEP",
            "else": "$$PRUNE"
        
    
])

因此返回整个元素以对 $setDifference 进行“集合”比较,以过滤掉任何返回的 false 值。然后对没有匹配的结果数组进行$size 测试,以查看是否满足必要的匹配,并通过$$PRUNE 丢弃不匹配的文档。

当然,$map 这里负责处理每个元素,要么返回整个原始元素,要么在不满足条件的地方返回 false

如果实际上在数组中存在重复的信息,比如“加利福尼亚”对计数很重要,那么未来的 MongoDB 版本将具有$filter,这两者都可以稍微简化流程,最重要的是在减少到时不会删除重复信息一个“集合”:

db.world.aggregate([
     "$match":  "country.3":  "$exists": true  ,
     "$redact": 
        "$cond": 
            "if": 
                "$gte": [
                     "$size":  "$filter": 
                        "input": "$country",
                        "as": "el",
                        "cond": 
                            "$eq": [ "$$el.type", 1 ]
                        
                    ,
                    4
                ]
            ,
            "then": "$$KEEP",
            "else": "$$PRUNE"
        
    
])

当然,在该版本可用之前,您需要使用更传统的方法,使用 $unwind$match 来过滤数组,同时保留重复项,然后首先通过 $group 获取“计数”:

db.world.aggregate([
     "$match":  "country.3":  "$exists": true  ,
     "$project":  "country": 1, "countryCopy": "$country"  ,
     "$unwind": "$country" ,
     "$match":  "country.type": 1  ,
     "$group": 
        "_id": "$_id",
        "country":  "$first": "$countryCopy" 
        "count":  "$sum": 1 
    ,
     "$match":  "count":  "$gte": 4  
])

但希望数组中没有重复项,所以没关系。

同样重要的是使用初始的$match 来立即过滤掉没有可能匹配的所需元素数量的数组(数组索引为n-1),方法是测试至少存在最小索引并丢弃正在处理的文档在这种情况下,总共少于 4 个元素。

这是$exists 测试,在这里很有用。这减少了在以后的处理中尝试匹配由于没有足够的元素开始而可能无法满足所需匹配计数的文档。

【讨论】:

以上是关于如何在MongoDB中查询数组中的字段超过n次的文档的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb-查询json数组中的字段是不是存在

如何根据MongoDB中文档字段中的每个数组项过滤集合

超过 500 万条记录的 MongoDB 查询性能

mongodb查询,如何只查询出某一个字段的值?

数组中字段的最小值和数组mongodb聚合查询中的第一个对象

在Java中查询具有完全匹配字段MongoDB的数组元素