在Mongo数组对象中计算对象属性存在的次数

4
我有一个这样的文档结构:
[{
 name: "Something",
 codes: [
         {type: 11}, 
         {type: 11}, 
         {type: 15}
        ]
 },
 {
 name: "Another",
 codes: [
         {type: 11}, 
         {type: 12}, 
         {type: 15},
         {type: 11}
        ]
 }]]

我需要在收藏中每个条目中计算 type = 11 出现的次数。但我卡住了。

3个回答

6
尽管可以应用$match来过滤仅包含特定类型代码的文档,但对于此特定问题陈述不应该这样做。因为它会从输出中过滤掉没有特定类型代码的文档。
你需要:
  • 基于codes字段对每个文档进行Unwind(展开)操作。
  • 如果codes字段是所需类型的,则project(投射)一个名为wantedType的字段,其值为1,否则为0
  • _id字段进行Group操作以获取wantedType字段的总和,这将告诉你特定文档中想要的类型代码的数量。
  • 因此,即使一个文档没有想要的类型代码,它也会在输出中呈现为0计数。
代码:
var typeCountToCalculate = 11;

db.collection.aggregate([
{$unwind:"$codes"},
{$project:{"name":1,
          "wantedType":{$cond:[{$eq:["$codes.type",typeCountToCalculate ]},1,0]}}},
{$group:{"_id":"$_id",
         "name":{$first:"$name"},"count":{$sum:"$wantedType"}}}
])

输出:

{
        "_id" : ObjectId("54ad79dae024832588b287f4"),
        "name" : "Another",
        "count" : 2
}
{
        "_id" : ObjectId("54ad79dae024832588b287f3"),
        "name" : "Something",
        "count" : 2
}

2
@EdgarMartinez 直到你意识到这是完全错误的,对于正在学习的人来说并不是很有信息量。这是一个糟糕的接受。 - Neil Lunn

6
MongoDB的聚合框架是解决问题的答案。关键操作包括$unwind,将数组内容处理为“规范化”的文档,以及$group管道阶段,用于获取计数。
还有针对$match管道阶段的优化。在查询开始时进行过滤,以筛选出不可能匹配的文档,在$unwind阶段后再次进行过滤,以删除那些肯定不符合条件的元素(现在是文档)。
db.collection.aggregate([
    // Match to filter documents
    { "$match": { "codes.type": 11 }},

    // Unwind to 'de-normalize'
    { "$unwind": "$codes" },

    // Match to filter again, but remove the array elements
    { "$match": { "codes.type": 11 }},

    // Count the occurrences of the the matches
    { "$group": {
        "_id": "$codes.type",
        "count": { "$sum": 1 }
    }}
])

自然地,如果你删除了所有的“匹配”,那么你就可以得到整个集合中每种“类型”的“计数”。

在现代版本中,你可以使用MongoDB 2.6及更高版本的$redact操作符来略微改变这一点。由于管道阶段的递归性质有点牵强,因此需要注意:

db.collection.aggregate([
    // Match to filter documents
    { "$match": { "codes.type": 11 }},

    // Filter out non matches
    { "$redact": {
        "$cond": {
            "if": { "$eq": [ 
                { "$ifNull": [ "$type", 11 ] },
                11
            ]},
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}

    // Unwind to 'de-normalize'
    { "$unwind": "$codes" },

    // Count the occurrences of the the matches
    { "$group": {
        "_id": "$codes.type",
        "count": { "$sum": 1 }
    }}
)

这是一种不同的过滤方式,如果您的服务器支持它,它就是完全有效的。但是,在使用嵌套级别的其他示例时要小心。

始终在进行任何其他操作之前,过滤出您想要处理的匹配值"first"。这将从聚合管道中删除不必要的工作。如果只有10,000个可能的匹配项,而且其中仅有2,000个元素与这些文档相匹配,那么处理100,000个文档就没有用处。


3

试试这个:

db.test.aggregate([
{$unwind : "$codes"},
{$match : { 'codes.type' : 11}} ,
{$group : { 
    _id : { 'name': '$name', 'type' : '$codes.type'}
    ,count: { $sum: 1 }
}},
]).result

输出结果将是:
{
    "0" : {
        "_id" : {
            "name" : "Another",
            "type" : 11
        },
        "count" : 2
    },
    "1" : {
        "_id" : {
            "name" : "Something",
            "type" : 11
        },
        "count" : 2
    }
}

好的观点包括匹配,但它所做的是不匹配没有type=11代码的文档。因此,在输出中,相应的文档计数将不会作为0被包括。 - BatScream

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接