我正在使用着名的餐厅系列在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"
}
我想计算grade.score的平均值,仅使用grade.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 :(得分: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
,其显示为"两次",一次创建"平均"文档中的值,其次是"平均累加器"对于分组键。
对于问题中显示的数据,您会得到:
{
"_id" : "Morris Park Bake Shop",
"avg" : 6.75
}
只有那些标有等级" A"
的条目的平均分数 有趣的是,这在单个文档中工作正常,但如果应用于https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json的完整数据集,则会产生 null
值,以获取实际上未累积的任何$avg
超过一个文件。
只需将过滤后的数组中的平均值添加到文档即可:
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
通常会应用于"否定"或"降序"顺序,首先表示最大值。或者至少在你检查数据和掌握聚合时最有意义。
看起来更短或可能更少混淆"并不意味着"更好" 。我们在$filter
管道中使用$map
和$group
操作编写此内容的原因是因为使用$unwind
进行处理的成本非常高。
使用$unwind
为每个阵列成员创建整个文档的副本,这通常相当于要处理的文档数量的大量增加,这当然会增加处理时间。
所以"更短的真实案例更好"实际上是在使用"较少的管道阶段"并且通过完全删除$unwind
的所有用法来不夸大要处理的文档数量。
在$match
之前添加$group
的原因是因为在另一个示例中,当您使用$unwind
进行处理时,任何空数组都将从要处理的文档中删除,并且然后其他后续的$match
将过滤掉没有"A"
成绩的任何内容以及确实有成绩但没有匹配"A"
的文档也会被删除。
因此,使用$map
和$filter
,这些文档将返回null
,因为这是从$avg
的空数组参数返回的值。但当然,如果最初的条件是"文件"必须包含匹配的条件,然后最初为空或过滤空的"数组永远不会被考虑,因为它们一开始就从处理中删除了。
作为任何聚合操作的黄金法则,包括某种程度的"过滤"是始终 $match
作为第一个管道阶段,因此只有那些对任何后续条件有效的文档才是唯一选择的文档。这也大大加快了它的速度。
注意:由于返回了
null
值,这导致我在疲惫状态下出现了一些相当大的恐慌。应该注意的是"通常"任何$group
操作的应用通常是积累和减少"结果很大。在问题中选择的
"$name"
字段对于从MongoDB文档示例中获取的sample dataset中的每个文档都非常独特。一个更现实的分组"样本将是从数据中使用"$cuisine"
,这实际上是在文档中累积的#34}。您通常使用