2017-09-14 12 views
1

EDIT: 使用例: 訪問者に関するサーバーからの報告が続きます。我々は、これらの「レポート」をMongoDBに挿入してから数秒後にサーバ上のデータを事前集計します。ネストされたドキュメントと配列を集約して縮小する

私たちのダッシュボードでは、時間範囲に基づいてさまざまなブラウザ、OS、ジオロケーション(国など)を照会したいと考えています。

このように:最近7日間で、Chromeを使用しているユーザーは1000人、ドイツは500人、イギリスは200人などでした。

私はダッシュボードに必要なMongoDBクエリに悩まされています。

{ 
    "_id" : ObjectId("59b9d08e402025326e1a0f30"), 
    "channel_perm_id" : "c361049fb4144b0e81b71c0b6cfdc296", 
    "source_id" : "insomnia", 
    "start_timestamp" : ISODate("2017-09-14T00:42:54.510Z"), 
    "end_timestamp" : ISODate("2017-09-14T00:42:54.510Z"), 
    "timestamp" : ISODate("2017-09-14T00:42:54.510Z"), 
    "resource_uri" : "b755d62a-8c0a-4e8a-945f-41782c13535b", 
    "sources_info" : { 
     "browsers" : [ 
      { 
       "name" : "Chrome", 
       "count" : NumberLong(2) 
      } 
     ], 
     "operating_systems" : [ 
      { 
       "name" : "Mac OS X", 
       "count" : NumberLong(2) 
      } 
     ], 
     "continent_ids" : [ 
      { 
       "name" : "EU", 
       "count" : NumberLong(1) 
      } 
     ], 
     "country_ids" : [ 
      { 
       "name" : "DE", 
       "count" : NumberLong(1) 
      } 
     ], 
     "city_ids" : [ 
      { 
       "name" : "Solingen", 
       "count" : NumberLong(1) 
      } 
     ] 
    }, 
    "unique_sources" : NumberLong(1), 
    "requests" : NumberLong(1), 
    "cache_hits" : NumberLong(0), 
    "cache_misses" : NumberLong(1), 
    "cache_hit_size" : NumberLong(0), 
    "cache_refill_size" : NumberLong("170000000000") 
} 

今、私たちはタイムスタンプに基づいてこれらのレポートを集約する必要があります。

は、我々は次のレポートエントリを持っています。 これまでのところ、そう簡単:

db.channel_report.aggregate([{ 
    $group: { 
    _id: { 
     $dateToString: { 
     format: "%Y", 
     date: "$timestamp" 
     } 
    }, 
    sources_info: { 
     $push: "$sources_info" 
    } 
    }, 
}]; 

しかし、今では私のために困難を取得します。すでに気付いたことがあるように、sources_infoオブジェクトが問題です。

すべてのソース情報をグループごとに配列に「プッシュ」するのではなく、実際にそれを累積する必要があります。

{ 
    sources_info: [ 
    { 
     browsers: [ 
     { 
      name: "Chrome, 
      count: 1 
     } 
     ] 
    }, 
    { 
     browsers: [ 
     { 
      name: "Chrome, 
      count: 1 
     } 
     ] 
    } 
    ] 
} 

配列はこれに削減する必要があります:私たちはこのようなものがあれば

だから、

{ 
    sources_info: 
    { 
     browsers: [ 
     { 
      name: "Chrome, 
      count: 2 
     } 
     ] 
    } 
} 

を私たちは、分析のためのMySQLからのMongoDBへの移行が、私はどのように見当もつかないモンゴでこの行動をモデル化する。ドキュメントに関しては、少なくとも現行のデータ構造では不可能だと思います。

いい解がありますか?あるいは、別の種類のデータ構造ですか?

乾杯、StriveCDN

+0

$ unwindを試してグルーピングしましたか? –

+0

試しましたが、実際には5つの異なるグループについて話しています。ブラウザのように、operating_system、conti ...などそれはちょっと間違って、遅いと感じる。あなたの計画をスケッチできるなら、それは非常に素晴らしいでしょう! – Kr0e

+0

より具体的にする必要があります。あなたのサンプル文書には ''ブラウザ '"よりもはるかに多くの部分がありますので、使用している' $ push'は各文書の各プロパティを結果配列にプッシュするだけです。これはまた、合理的なサイズのデータ​​を破ることにもつながります。だから、あなたが実際に期待しているものが、その中の「すべての鍵」であるならば、本当にあなたの質問で言いたいことがあります。これらのプロパティのいずれかがなぜ配列そのものにあるのかについては、本当に不明です。だからあなたの意図した結果を精巧に説明し完全に説明することは害ではないでしょう。 –

答えて

1

から クリスは、あなたが持っている基本的な問題は、あなたがおそらく本当に一貫性のある属性のパスに値を使用して代わりにする必要があります「という名前のキーを」使用していることです。つまり、"browsers"のようなキーの代わりに、これはおそらく単に各エントリの"type": "browser"となるはずです。

この理由は、データを集約する一般的なアプローチで明らかになります。それはまた、一般的にクエリに役立ちます。しかし、基本的には、最初のデータ形式をこの種の構造に強制して、最初に集約します。

最新のリリースでは

(MongoDBの3.4.4およびそれ以上)、我々は$objectToArrayを経由して、あなたの名前のキーと連携し、次のように操作することができます。

db.channel_report.aggregate([ 
    { "$project": { 
    "timestamp": 1, 
    "sources": { 
     "$reduce": { 
     "input": { 
      "$map": { 
      "input": { "$objectToArray": "$sources_info" }, 
      "as": "s", 
      "in": { 
       "$map": { 
       "input": "$$s.v", 
       "as": "v", 
       "in": { 
        "type": "$$s.k", 
        "name": "$$v.name", 
        "count": "$$v.count"  
       } 
       } 
      } 
      }  
     }, 
     "initialValue": [], 
     "in": { "$concatArrays": ["$$value", "$$this"] } 
     } 
    } 
    }}, 
    { "$unwind": "$sources" }, 
    { "$group": { 
    "_id": { 
     "year": { "$year": "$timestamp" }, 
     "type": "$sources.type", 
     "name": "$sources.name" 
    }, 
    "count": { "$sum": "$sources.count" } 
    }}, 
    { "$group": { 
    "_id": { "year": "$_id.year", "type": "$_id.type" }, 
    "v": { "$push": { "name": "$_id.name", "count": "$count" } } 
    }}, 
    { "$group": { 
    "_id": "$_id.year", 
    "sources_info": { 
     "$push": { "k": "$_id.type", "v": "$v" } 
    } 
    }}, 
    { "$addFields": { 
    "sources_info": { "$arrayToObject": "$sources_info" } 
    }} 
]) 

MongoDBの3にノッチをそれをバック取ります。4(今ではほとんどのホスティングサービスではデフォルトであるべきである)あなたが交互に手動で各キーの名前を宣言することができます:

db.channel_report.aggregate([ 
    { "$project": { 
    "timestamp": 1, 
    "sources": { 
     "$concatArrays": [ 
     { "$map": { 
      "input": "$sources_info.browsers", 
      "in": { 
      "type": "browsers", 
      "name": "$$this.name", 
      "count": "$$this.count" 
      } 
     }}, 
     { "$map": { 
      "input": "$sources_info.operating_systems", 
      "in": { 
      "type": "operating_systems", 
      "name": "$$this.name", 
      "count": "$$this.count" 
      } 
     }}, 
     { "$map": { 
      "input": "$sources_info.continent_ids", 
      "in": { 
      "type": "continent_ids", 
      "name": "$$this.name", 
      "count": "$$this.count" 
      } 
     }}, 
     { "$map": { 
      "input": "$sources_info.country_ids", 
      "in": { 
      "type": "country_ids", 
      "name": "$$this.name", 
      "count": "$$this.count" 
      } 
     }}, 
     { "$map": { 
      "input": "$sources_info.city_ids", 
      "in": { 
      "type": "city_ids", 
      "name": "$$this.name", 
      "count": "$$this.count" 
      } 
     }} 
     ] 
    } 
    }}, 
    { "$unwind": "$sources" }, 
    { "$group": { 
    "_id": { 
     "year": { "$year": "$timestamp" }, 
     "type": "$sources.type", 
     "name": "$sources.name" 
    }, 
    "count": { "$sum": "$sources.count" } 
    }}, 
    { "$group": { 
    "_id": { "year": "$_id.year", "type": "$_id.type" }, 
    "v": { "$push": { "name": "$_id.name", "count": "$count" } } 
    }}, 
    { "$group": { 
    "_id": "$_id.year", 
    "sources": { 
     "$push": { "k": "$_id.type", "v": "$v" } 
    } 
    }}, 
    { "$project": { 
    "sources_info": { 
     "browsers": { 
     "$arrayElemAt": [ 
      "$sources.v", 
      { "$indexOfArray": [ "$sources.k", "browsers" ] } 
     ]  
     }, 
     "operating_systems": { 
     "$arrayElemAt": [ 
      "$sources.v", 
      { "$indexOfArray": [ "$sources.k", "operating_systems" ] } 
     ]  
     }, 
     "continent_ids": { 
     "$arrayElemAt": [ 
      "$sources.v", 
      { "$indexOfArray": [ "$sources.k", "continent_ids" ] } 
     ]  
     }, 
     "country_ids": { 
     "$arrayElemAt": [ 
      "$sources.v", 
      { "$indexOfArray": [ "$sources.k", "country_ids" ] } 
     ]  
     }, 
     "city_ids": { 
     "$arrayElemAt": [ 
      "$sources.v", 
      { "$indexOfArray": [ "$sources.k", "city_ids" ] } 
     ]  
     } 
    }  
    }} 
]) 

我々は$indexOfArrayの代わりに$map$filterを使用してのMongoDB 3.2にバックアップしても風をすることができますが、説明するのが一般的なアプローチです。

を連結アレイ

起こる必要がある主なものは、指定されたキーを使用して、多くの異なるアレイからデータを取得し、各キーの名前を表す"type"プロパティを持つ「単一のアレイ」を作ることです。あなたがしたいデータの一部

/* 1 */ 
{ 
    "_id" : ObjectId("59b9d08e402025326e1a0f30"), 
    "timestamp" : ISODate("2017-09-14T00:42:54.510Z"), 
    "sources" : [ 
     { 
      "type" : "browsers", 
      "name" : "Chrome", 
      "count" : NumberLong(2) 
     }, 
     { 
      "type" : "operating_systems", 
      "name" : "Mac OS X", 
      "count" : NumberLong(2) 
     }, 
     { 
      "type" : "continent_ids", 
      "name" : "EU", 
      "count" : NumberLong(1) 
     }, 
     { 
      "type" : "country_ids", 
      "name" : "DE", 
      "count" : NumberLong(1) 
     }, 
     { 
      "type" : "city_ids", 
      "name" : "Solingen", 
      "count" : NumberLong(1) 
     } 
    ] 
} 

アンワインドとグループ

:これは、データが最初の場所に格納する必要があるか間違いなくある、とアプローチのいずれかの最初の集約の段階は次のように出てきます実際に累積するには、アレイ内の「"type"」および「"name"」のプロパティが含まれます。 「配列内」の文書間で累積する必要がある場合は、グルーピングキーの一部としてこれらの値にアクセスできるように、使用するプロセスは$unwindです。これが何を意味するのか

を組み合わせアレイ上$unwindを使用した後、あなたはその後、$sum"count"値に順番にこれらのキーと減少"timestamp"細部の両方に$groupにしたいということです。

「サブレベル」の詳細(ブラウザ内の各ブラウザの名前)があるので、追加の$groupパイプラインステージを使用し、グループ化キーの細分性を徐々に減らし、$pushを使用して配列に詳細を累積します。それは、もともとあったように同じ形式で表現されていないが、これは実際に、データの最終状態である

/* 1 */ 
{ 
    "_id" : 2017, 
    "sources_info" : [ 
     { 
      "k" : "continent_ids", 
      "v" : [ 
       { 
        "name" : "EU", 
        "count" : NumberLong(1) 
       } 
      ] 
     }, 
     { 
      "k" : "city_ids", 
      "v" : [ 
       { 
        "name" : "Solingen", 
        "count" : NumberLong(1) 
       } 
      ] 
     }, 
     { 
      "k" : "country_ids", 
      "v" : [ 
       { 
        "name" : "DE", 
        "count" : NumberLong(1) 
       } 
      ] 
     }, 
     { 
      "k" : "browsers", 
      "v" : [ 
       { 
        "name" : "Chrome", 
        "count" : NumberLong(2) 
       } 
      ] 
     }, 
     { 
      "k" : "operating_systems", 
      "v" : [ 
       { 
        "name" : "Mac OS X", 
        "count" : NumberLong(2) 
       } 
      ] 
     } 
    ] 
} 

:いずれの場合も

、出力の非常に最後の段階を省略し、蓄積構造は、として出てきます見つかりました。これ以上の処理は単に名前付きキーとして出力するだけの美容であるため、この時点では間違いなく完全です。名前のキーを様々なアプローチがいずれか、または名前付きキーを持つオブジェクトに戻って配列の内容を変換するために$arrayToObjectを使用して一致するキー名で、配列のエントリを探している示されているように

出力。当然の凝集パイプラインアプローチのいずれかに適用

db.channel_report.aggregate([ 
    { "$project": { 
    "timestamp": 1, 
    "sources": { 
     "$reduce": { 
     "input": { 
      "$map": { 
      "input": { "$objectToArray": "$sources_info" }, 
      "as": "s", 
      "in": { 
       "$map": { 
       "input": "$$s.v", 
       "as": "v", 
       "in": { 
        "type": "$$s.k", 
        "name": "$$v.name", 
        "count": "$$v.count"  
       } 
       } 
      } 
      }  
     }, 
     "initialValue": [], 
     "in": { "$concatArrays": ["$$value", "$$this"] } 
     } 
    } 
    }}, 
    { "$unwind": "$sources" }, 
    { "$group": { 
    "_id": { 
     "year": { "$year": "$timestamp" }, 
     "type": "$sources.type", 
     "name": "$sources.name" 
    }, 
    "count": { "$sum": "$sources.count" } 
    }}, 
    { "$group": { 
    "_id": { "year": "$_id.year", "type": "$_id.type" }, 
    "v": { "$push": { "name": "$_id.name", "count": "$count" } } 
    }}, 
    { "$group": { 
    "_id": "$_id.year", 
    "sources_info": { 
     "$push": { "k": "$_id.type", "v": "$v" } 
    } 
    }}, 
    /* 
    { "$addFields": { 
    "sources_info": { "$arrayToObject": "$sources_info" } 
    }} 
    */ 
]).map(d => Object.assign(d,{ 
    "sources_info": d.sources_info.reduce((acc,curr) => 
    Object.assign(acc,{ [curr.k]: curr.v }),{}) 
})) 

代替的には、シェルにカーソル結果を操作する本.map()例によって示されるように、単に、コードにその最後の操作を行うことです。

もちろんも$concatArraysであればすべてのエントリが(それらがあるように見えるように)"name""type"の一意の識別の組み合わせを有し、かつ、処理することにより、最終的な出力を変更するアプリケーションと手段として$setUnionで置換することができますカーソルをMongoDB 2.6のように遠くまで適用することもできます。

最終出力

最終出力(実際には当然の集合が、質問サンプルのみ一つの文書は)として示すように、最後のサンプル出力からすべてのサブキーに蓄積し、再構成:

{ 
    "_id" : 2017, 
    "sources_info" : { 
     "continent_ids" : [ 
      { 
       "name" : "EU", 
       "count" : NumberLong(1) 
      } 
     ], 
     "city_ids" : [ 
      { 
       "name" : "Solingen", 
       "count" : NumberLong(1) 
      } 
     ], 
     "country_ids" : [ 
      { 
       "name" : "DE", 
       "count" : NumberLong(1) 
      } 
     ], 
     "browsers" : [ 
      { 
       "name" : "Chrome", 
       "count" : NumberLong(2) 
      } 
     ], 
     "operating_systems" : [ 
      { 
       "name" : "Mac OS X", 
       "count" : NumberLong(2) 
      } 
     ] 
    } 
} 

sources_infoの各キーの配列エントリは、同じ"name"を共有する他のすべてのエントリの累積カウントに縮小されます。

関連する問題