计算包含字段的文档数量

Dis*_*ana 6 mongodb mongodb-query aggregation-framework

我有这三个MongoDB文档:

{ 
    "_id" : ObjectId("571094afc2bcfe430ddd0815"), 
    "name" : "Barry", 
    "surname" : "Allen", 
    "address" : [
        {
            "street" : "Red", 
            "number" : NumberInt(66), 
            "city" : "Central City"
        }, 
        {
            "street" : "Yellow", 
            "number" : NumberInt(7), 
            "city" : "Gotham City"
        }
    ]
}

{ 
    "_id" : ObjectId("57109504c2bcfe430ddd0816"), 
    "name" : "Oliver", 
    "surname" : "Queen", 
    "address" : {
        "street" : "Green", 
        "number" : NumberInt(66), 
        "city" : "Star City"
    }
}
{ 
    "_id" : ObjectId("5710953ac2bcfe430ddd0817"), 
    "name" : "Tudof", 
    "surname" : "Unknown", 
    "address" : "homeless"
}
Run Code Online (Sandbox Code Playgroud)

address字段是Array第一个文档中的对象,Object第二个String是第三个,第三个是第三个.我的目标是找到我的收藏中有多少文件包含该字段address.street.在这种情况下,正确的计数是1,但我的查询得到两个:

db.coll.find({"address.street":{"$exists":1}}).count()
Run Code Online (Sandbox Code Playgroud)

我也试过map/reduce.它有效,但速度较慢; 所以,如果可能的话,我会避免它.

Bla*_*ven 8

这里的区别在于,.count()在返回字段存在的"文档"计数时,操作实际上是"正确的".因此,一般考虑因素分解为:

如果您只想使用数组字段排除文档

然后,最有效的方法是将"街道"作为"地址"的属性作为"数组"排除那些文件,然后只使用寻找0索引的点符号属性在exlcusion中不存在:

db.coll.find({
  "address.street": { "$exists": true },
  "address.0": { "$exists": false }
}).count()
Run Code Online (Sandbox Code Playgroud)

在两种情况下$exists,本机编码的操作员测试都能正确地完成工作并且有效.

如果你打算计算现场出现次数

如果您实际问的是"字段计数",其中某些"文档"包含数组条目,其中"字段"可能会多次出现.

为此,您需要像您提到的聚合框架或mapReduce.MapReduce使用基于JavaScript的处理,因此比.count()操作慢得多.聚合框架还需要计算并且"将"慢于.count()mapReduce,但不会像mapReduce那样慢.

在MongoDB 3.2中,您可以通过扩展$sum处理值数组以及作为分组累加器的能力获得一些帮助.这里的另一个帮助是当数据实际上是"数组"时$isArray允许不同的处理方法$map:

db.coll.aggregate([
  { "$group": {
    "_id": null,
    "count": {
      "$sum": {
        "$sum": {
          "$cond": {
            "if": { "$isArray": "$address" },
            "then": {
              "$map": {
                "input": "$address",
                "as": "el",
                "in": {
                  "$cond": {
                    "if": { "$ifNull": [ "$$el.street", false ] },
                    "then": 1,
                    "else": 0
                  }
                }
              }
            },
            "else": {
              "$cond": {
                "if": { "$ifNull": [ "$address.street", false ] },
                "then": 1,
                "else": 0
              }
            }
          }
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

早期版本依赖于更多的条件处理,以便以不同方式处理数组和非数组数据,并且通常需要$unwind处理数组条目.

使用$mapMongoDB 2.6 转换数组:

db.coll.aggregate([
  { "$project": {
    "address": {
      "$cond": {
        "if": { "$ifNull": [ "$address.0", false ] },
        "then": "$address",
        "else": {
          "$map": {
            "input": ["A"],
            "as": "el",
            "in": "$address"
          }
        }
      }
    }
  }},
  { "$unwind": "$address" },
  { "$group": {
    "_id": null,
    "count": {
      "$sum": {
        "$cond": {
          "if": { "$ifNull": [ "$address.street", false ] },
          "then": 1,
          "else": 0
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

或者使用MongoDB 2.2或2.4提供条件选择:

db.coll.aggregate([
  { "$group": {
    "_id": "$_id",
    "address": { 
      "$first": {
        "$cond": [
          { "$ifNull": [ "$address.0", false ] },
          "$address",
          { "$const": [null] }
        ]
      }
    },
    "other": {
      "$push": {
        "$cond": [
          { "$ifNull": [ "$address.0", false ] },
          null,
          "$address"
        ]
      }
    },
    "has": { 
      "$first": {
        "$cond": [
          { "$ifNull": [ "$address.0", false ] },
          1,
          0
        ]
      }
    }
  }},
  { "$unwind": "$address" },
  { "$unwind": "$other" },
  { "$group": {
    "_id": null,
    "count": {
      "$sum": {
        "$cond": [
          { "$eq": [ "$has", 1 ] },
          { "$cond": [
            { "$ifNull": [ "$address.street", false ] },
            1,
            0
          ]},
          { "$cond": [
            { "$ifNull": [ "$other.street", false ] },
            1,
            0
          ]}
        ]
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

所以后者的形式"应该"比mapReduce好一点,但可能不是很多.

在所有情况下,逻辑都倾向于使用聚合框架$ifNull的"逻辑"形式$exists.配对$cond,当属性实际存在时获得"真实"结果,并且false当不存在时返回值.这确定是否10分别返回到经由所述整体积累$sum.

理想情况下,您拥有可在单个$group管道阶段执行此操作的现代版本,但您需要更长的路径.