mongodb - 计算有多少文档包含一个字段

标签 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"
}

address 字段是第一个文档中的对象的 Array、第二个文档中的 ObjectString在第三。 我的目标是找出我的集合中有多少文档包含字段 address.street。在这种情况下,正确的计数是 1,但通过我的查询,我得到了两个:

db.coll.find({"address.street":{"$exists":1}}).count()

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

最佳答案

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

如果你只想排除带有数组字段的文档

那么最有效的方法是排除那些“街道”是“地址”属性的文档作为“数组”,然后只需使用查找 0 的点符号属性> 排除项中不存在的索引:

db.coll.find({
  "address.street": { "$exists": true },
  "address.0": { "$exists": false }
}).count()

在这两种情况下,$exists 都作为 native 编码的运算符测试执行正确且高效的工作。

如果您打算计算字段出现次数

如果您实际询问的是“字段计数”,其中一些“文档”包含数组条目,其中该“字段”可能出现多次。

为此,您需要您提到的聚合框架或 mapReduce。 MapReduce 使用基于 JavaScript 的处理,因此比 .count() 操作要慢得多。聚合框架还需要计算并且“将”比 .count() 慢,但不会像 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
              }
            }
          }
        }
      }
    }
  }}
])

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

通过 $map 转置数组使用 MongoDB 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
        }
      }
    }
  }}
])

或者使用 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
          ]}
        ]
      }
    }
  }}
])

所以后一种形式“应该”比 mapReduce 表现好一点,但可能不会好很多。

在所有情况下,逻辑都归结为使用 $ifNull作为聚合框架的 $exists 的“逻辑”形式。与 $cond 配对,当属性实际存在时得到一个“truthfull”结果,不存在时返回一个false值。这决定了是1还是0分别通过$sum返回到整体累加中。 .

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

关于mongodb - 计算有多少文档包含一个字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36645838/

相关文章:

ruby-on-rails - 使用 MongoMapper 继承 : searching the parent class

c# - 如果元素名称以句点结尾,则发生 BsonSerializationException

Mongodb 计算集合中某个值存在的频率

arrays - 在 mongodb 数组中查找最接近的值

javascript - mongodb存储标签,应该使用联合字符串还是数组?

node.js - 如何在 Mongoose 中查询一组具有值数组的对象?

javascript - 如何使用 Node js 和 mongoose 创建唯一的 json 元素列表

javascript - Mongodb点字段更新

mongodb - 无法识别的管道阶段名称 : '$sample'

mongodb - Mongo 按条件分组和计数