2017-09-01 7 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.grade =「A」と要素のみを使用して、grades.scoreの平均を計算します。私がやっていることは、

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オペレータが別のパイプラインステージはありません。配列としてプロパティを返すだけです。最高の$avgに引数として$groupに直接このケースでは使われています:

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 } } 
]) 

はまた、唯一の"score"値を抽出し、「二回」と表示され$avg、それらを養うために$mapを適用し、一度あることは、「作成します文書内の平均値、第2に、グループ化キーの「平均アキュムレータ」である。質問に示されたデータについては

は、あなたが得る:

グレード「A」でマークされたエントリのみから平均スコアである
{ 
    "_id" : "Morris Park Bake Shop", 
    "avg" : 6.75 
} 


は、興味深いことに、これは単一のドキュメントで正常に動作しますが、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 } } 
]) 

最適なものをやると、実際に「リンゴとリンゴを」比較することはプレフィルタすることですいずれかが$sortは通常に適用される。また

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 } } 
]) 

:おそらく初期の段階で$matchで基準に一致する配列要素を持つことができる唯一の人のため「のドキュメント」 「負」または「降順」の順番で、最初に最大値を示します。少なくとも、データを検査して一般的な集約をするときには、最も意味があります。

これよりも短いか、おそらく「あまり混乱しない」とは、が「より良い」という意味ではありません。ここにあります。 $filter$map操作を$groupパイプライン内に記述する理由は、$unwindの処理が非常にコストがかかるためです。

$unwindを使用すると、配列メンバーごとにドキュメント全体のコピーが作成されます。これは、一般に、処理するドキュメントの数が大幅に増加するため、処理にかなりの時間を要します。

したがって、「短い方が良い」という実際のケースは、実際には「パイプライン段階が少ない」ことです。処理するドキュメントの数を膨らませないことで、すべての使用量をすべて削除して処理します。$unwind

あなたは$unwindで処理するときに、他の例では、任意の空の配列は、その後、他の後続$matchそこにフィルタリングすることは何もどこ処理する文書から除去することになるので$group$matchに追加するための理由があります"A"のグレードはなく、グレードはあったが一致するものは"A"と一致していなかったドキュメントもそこで削除されていました。

その引数の空の配列から$avgに返される値ですので、そう$map$filterを使用して、これらの文書はnullを返します。もちろん、最初の条件が「文書」に一致する条件が含まれていなければならない場合、最初は空であるか、または「フィルタリングされた空の」配列は処理の最初から削除されているため考慮されません。それ以降の任意の条件に対して有効になります文書だけが唯一のものになるように、「フィルタリング」のいくつかの程度を含む任意の集計作業の黄金のルールは、常に$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

私は理論だと思うが、これを実行したときに、私はこのエラーを取得[OK]:例外:無効な演算子「$の平均」 – user3174311

+0

user3174311 @あなたは私はあなたが、あなたがする必要があるすべてをしたと思うどこからデータを取得した場合doはコピー&ペーストです。データソースに注目すると、私はいくつかの奇妙な動作に気がついたので、実際に何らかの調査を行っていました。この –

+0

についての答えにメモを追加しました。最後の例では、構文がさらにはっきりしています。ありがとうございます。 – user3174311

関連する問題