2017-09-01 89 views
0

我正在使用着名的餐厅系列在mongoDB上练习。我有这样一个记录列表:如何筛选数组值并获得平均值

{ 
"_id" : ObjectId("59a5211e107765480896f3e5"), 
"address" : { 
    "building" : "1007", 
    "coord" : [ 
     -73.856077, 
     40.848447 
    ], 
    "street" : "Morris Park Ave", 
    "zipcode" : "10462" 
}, 
"borough" : "Bronx", 
"cuisine" : "Bakery", 
"grades" : [ 
    { 
     "date" : ISODate("2014-03-03T00:00:00Z"), 
     "grade" : "A", 
     "score" : 2 
    }, 
    { 
     "date" : ISODate("2013-09-11T00:00:00Z"), 
     "grade" : "A", 
     "score" : 6 
    }, 
    { 
     "date" : ISODate("2013-01-24T00:00:00Z"), 
     "grade" : "A", 
     "score" : 10 
    }, 
    { 
     "date" : ISODate("2011-11-23T00:00:00Z"), 
     "grade" : "A", 
     "score" : 9 
    }, 
    { 
     "date" : ISODate("2011-03-10T00:00:00Z"), 
     "grade" : "B", 
     "score" : 14 
    } 
], 
"name" : "Morris Park Bake Shop", 
"restaurant_id" : "30075445" 
} 

我要计算的平均grades.score,只使用元素与grades.grade =“A”。我在做什么是

db.restaurants.aggregate([{$unwind: '$grades'}, {$filter: {input: '$grades.grade', as: 'grade', cond: {'$$grade': 'A'}}}, {$group: {_id: '$name', 'avg': {'$avg': '$grades.score'}}}, {$sort: {'avg': 1}}]) 

怎么了?

感谢

回答

0

$filter运营商是不是一个独立的流水线阶段。它只是返回一个数组作为属性。所以最好在这种情况下,直接用于$group作为参数传递给$avg

db.restaurants.aggregate([ 
    { '$group': { 
    '_id': '$name', 
    'avg': { 
     '$avg': { 
     '$avg': { 
      '$map': { 
      'input': { 
       '$filter': { 
       'input': '$grades', 
       'as': 'grade', 
       'cond': { '$eq': [ '$$grade.grade', 'A' ] } 
       } 
      }, 
      'as': 'grade', 
      'in': '$$grade.score' 
      } 
     } 
     } 
    } 
    }}, 
    { '$sort': { 'avg': 1 } } 
]) 

也适用$map只提取"score"值和养活他们$avg,从而出现“两次”,是一次以创建“ avg“值,其次是分组密钥的”平均累加器“。

对于这个问题显示的数据,您可以:

{ 
    "_id" : "Morris Park Bake Shop", 
    "avg" : 6.75 
} 

这是从只标有“A”级的条目平均分。


有趣的是能正常工作的一份文件中,但如果从https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json给整个数据集,这是获得实际未累积超过一个文件的任何$avg生产null值。

简单地增加从过滤的阵列到文档的平均值正常工作:

db.restaurants.aggregate([ 
    { "$addFields": { 
    "average": { 
     "$avg": { 
     "$map": { 
      "input": { 
      "$filter": { 
       "input": "$grades", 
       "as": "g", 
       "cond": { "$eq": [ "$$g.grade", "A" ] }  
      } 
      }, 
      "as": "g", 
      "in": "$$g.score" 
     } 
     } 
    } 
    }} 
]) 

作为不积累在一个以上的文件。即对"cuisine"

db.restaurants.aggregate([ 
    { '$group': { 
    '_id': '$cuisine', 
    'avg': { 
     '$avg': { 
     '$avg': { 
      '$map': { 
      'input': { 
       '$filter': { 
       'input': '$grades', 
       'as': 'g', 
       'cond': { '$eq': [ '$$g.grade', 'A' ] } 
       } 
      }, 
      'as': 'g', 
      'in': '$$g.score' 
      } 
     } 
     } 
    } 
    }}, 
    { '$sort': { 'avg': 1 } } 
]) 

这意味着原先规定的工作且令人信服确实当值被“划分为”实际发生在一个以上的文件。

遗憾的是,无论有多少文件被分组,所以可应用的唯一可靠方法仍然是应用$unwind。这真是现代的版本不应该是必要的:

虽然这将与一致的结果工作:

db.restaurants.aggregate([ 
    { "$match": { "grades.grade": "A" } }, 
    { "$unwind": "$grades" }, 
    { "$match": { "grades.grade": "A" } }, 
    { "$group": { 
    "_id": "$name", 
    "score": { "$avg": "$grades.score" } 
    }}, 
    { "$sort": { "score": -1 } } 
]) 

最优化的事情,实际上比较“与苹果苹果”是预过滤器的任何“文档”为只有那些都不可能匹配与$match在初始阶段的标准一个数组元素:

db.restaurants.aggregate([ 
    { '$match': { 'grades.grade': 'A' } }, 
    { '$group': { 
    '_id': '$name', 
    'value': { 
     '$avg': { 
     '$avg': { 
      '$map': { 
      'input': { 
       '$filter': { 
       'input': '$grades', 
       'as': 'g', 
       'cond': { '$eq': [ '$$g.grade', 'A' ] } 
       } 
      }, 
      'as': 'g', 
      'in': '$$g.score' 
      } 
     } 
     } 
    } 
    }}, 
    { "$sort": { "value": -1 } } 
]) 

另外,$sort将典型地在所施加的“负”或“降序”顺序,首先表示最大值。或者至少当你检查数据并且总体掌握聚合时最有意义。

所以看起来更短或可能“不太令人困惑”并不意味着“更好”在这里。我们在$group流水线中使用$filter$map操作编写此操作的原因是因为使用$unwind进行处理的代价非常高。

使用$unwind会为每个数组成员创建一个整个文档的副本,这通常意味着要处理的文档数量大量增加,这当然会增加处理时间。

因此,“更短更好”的实际情况实际上是在“少管线阶段”的使用中,并且通过完全去除所有使用的$unwind来不增加要处理的文档的数量。

的原因$group之前添加的$match是因为在其他的例子,当你与$unwind处理任何空数组会从文件删除处理,然后其他后续$match会有过滤掉任何地方没有"A"等级和文档确实有分数,但没有匹配"A"的文档也将在那里被删除。

因此,使用$map$filter,这些文件将返回null,因为这是从参数一个空数组$avg返回值。但是,当然如果最初的条件是“文档”必须包含匹配条件,那么最初为空或“过滤空”数组将不会被考虑,因为它们从一开始就从处理中移除。

作为使得包括一定程度的“过滤”任何聚合操作的金科玉律是总是$match作为第一个流水线级,以便只将适用于以后的任何条件的文件是唯一选择。这也加速了它自己的事情。

NOTE: This caused me some considerable panic in a tired state because of the null values returned. It should be noted that the "usual" application of any $group operation is typically to accumulate and "reduce" results considerably.

The "$name" field chosen in the question is pretty much unique for each document in the sample dataset obtained from the MongoDB documentation samples. A more realistic "grouping" sample would be to use "$cuisine" from the data, which actually accumulates "across documents" for which you typically use

+0

好,我觉得我得到了理论,但在执行这个,当我得到这个错误:异常:无效运算符“$平均” – user3174311

+0

@ user3174311如果你得到了,我认为你做那么数据都需要做的是复制和粘贴。注意到数据源,其实我只是在做一些调查,因为我注意到了一些奇怪的行为。在关于这个 –

+0

的回答中添加了注释,实际上在你的最后一个例子中,语法更加清晰,谢谢。 – user3174311