Mongodb 如何筛选数组值并获得平均值

Mongodb 如何筛选数组值并获得平均值,mongodb,aggregation-framework,Mongodb,Aggregation Framework,我正在使用著名的餐厅系列在mongoDB上练习。我有一张这样的记录列表: { "_id" : ObjectId("59a5211e107765480896f3e5"), "address" : { "building" : "1007", "coord" : [ -73.856077, 40.848447 ], "street" : "Morris Park Ave", "zipcode" : "10462" }, "bor

我正在使用著名的餐厅系列在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}}])
怎么了

谢谢

操作员不是单独的管道阶段。它只是返回一个数组作为属性。在这种情况下,最好直接在中使用它作为参数:

也适用于仅提取
“score”
值,并将其馈送至,出现“两次”,一次用于在文档中创建“avg”值,另一次用于分组键的“平均累加器”

对于问题中显示的数据,您可以得到:

{
    "_id" : "Morris Park Bake Shop",
    "avg" : 6.75
}
这是仅标记为“A”级的条目的平均分数


有趣的是,这在单个文档中可以很好地工作,但如果应用于完整的数据集,则会生成
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"
        }
      }
    }
  }}
])
在多个文档上进行累积也是如此。i、 e对于
“烹饪”

这意味着,当“分组用于”的值实际出现在多个文档中时,它将按照最初的说明和明显的方式工作

不幸的是,不管有多少文档被分组,唯一可靠的方法仍然是应用
$unwind
。在现代版本中确实不需要:

虽然这将产生一致的结果:

db.restaurants.aggregate([
  { "$match": { "grades.grade": "A" } },
  { "$unwind": "$grades" },
  { "$match": { "grades.grade": "A" } },
  { "$group": {
     "_id": "$name",
     "score": { "$avg": "$grades.score" } 
  }},
  { "$sort": { "score": -1 } }
])
要做并实际比较“苹果与苹果”的最理想的事情是预筛选任何“文档”,只筛选那些可能具有与初始阶段的条件匹配的数组元素的文档:

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 } }
])
此外,通常以“负”或“降序”顺序应用,首先指示最大值。或者,至少在您检查数据并开始处理聚合时,这是最有意义的

因此,看起来更短或可能“更少混淆”并不意味着这里的“更好”。我们之所以在管道中使用和操作编写此文件,是因为使用的处理成本极高

使用会为每个数组成员创建整个文档的副本,这通常会导致要处理的文档数量大幅增加,这当然会增加大量处理时间

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

在之前添加a的原因是,在另一个示例中,当您使用任何空数组进行处理时,将从要处理的文档中删除,然后另一个后续的示例将筛选出没有
“a”
等级的任何文档以及有等级但没有匹配
“a”的文档
也会被删除

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

任何包含某种程度的“过滤”的聚合操作的黄金法则是始终将作为第一个管道阶段,因此只有对任何后续条件有效的文档才会被选中。这本身也大大加快了速度

注意:由于返回的
null
值,这在疲劳状态下引起了我相当大的恐慌。应该注意的是,任何
$group
操作的“通常”应用通常是累积和“减少”结果

问题中选择的
“$name”
字段对于从MongoDB文档样本中获取的文档中的每个文档都是非常唯一的。一个更现实的“分组”示例是从数据中使用
“$courine”
,这实际上是在您通常使用的“跨文档”中累积的


好的,我想我得到了理论,但是当执行这个时,我得到了这个错误:异常:无效运算符“$avg”@user3174311如果你从我认为你所做的地方得到数据,那么你需要做的就是复制和粘贴。注意到数据来源,我实际上只是在做一些调查,因为我注意到一些奇怪的行为。在答案中添加了关于这一点的注释实际上,在您的最后一个示例中,语法更加清晰,谢谢。@user3174311您确实应该回来看看。尽管我最初在疲劳状态下感到恐慌,但一切都按照设计正常工作。您可能会发现使用
$unwind
进行处理更容易理解,但这确实不是您应该做的。因此,答案准确地展开了“为什么”您不应该使用
$unwind
,以及您在学习如何做这些事情时真正应该做什么。
db.restaurants.aggregate([
  { "$match": { "grades.grade": "A" } },
  { "$unwind": "$grades" },
  { "$match": { "grades.grade": "A" } },
  { "$group": {
     "_id": "$name",
     "score": { "$avg": "$grades.score" } 
  }},
  { "$sort": { "score": -1 } }
])
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 } }
])