sql >> データベース >  >> NoSQL >> MongoDB

Mongodb集計クエリ、または複雑すぎますか?

    あなたの質問ではもっと明確にされるべきでしたが、ソースからの出力サンプルはあなたが探していることを示唆しています:

    • 「uid」ごとのメッセージの総数
    • 「to」の値の個別の数
    • 「from」の値の個別の数
    • 各「uid」の「時間」あたりのカウントの概要

    これはすべて単一の集計ステートメントで可能であり、個別のリストを注意深く管理してから、24時間の各時間の結果をマッピングするための操作が必要です。

    ここでの最善のアプローチは、MongoDB3.2で導入されたオペレーターによって支援されます:

    db.collection.aggregate([
        // First group by hour within "uid" and keep distinct "to" and "from"
        { "$group": {
            "_id": {
                "uid": "$uid",
                "time": { "$hour": "$timestamp" }
            },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "count": { "$sum": 1 }
        }},
    
        // Roll-up to "uid" and keep each hour in an array
        { "$group": {
            "_id": "$_id.uid",
            "total": { "$sum": "$count" },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "temp_hours": { 
                "$push": {
                    "index": "$_id.time",
                    "count": "$count"
                }
            }
         }},
    
         // Getting distinct "to" and "from" requires a double unwind of arrays
         { "$unwind": "$to" },
         { "$unwind": "$to" },
         { "$unwind": "$from" },
         { "$unwind": "$from" },
    
         // And then adding back to sets for distinct
         { "$group": {
            "_id": "$_id",
            "total": { "$first": "$total" },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "temp_hours": { "$first": "$temp_hours" }
         }},
    
         // Map out for each hour and count size of distinct lists
         { "$project": {
            "count": "$total",
            "from_count": { "$size": "$from" },
            "to_count": { "$size": "$to" },
            "hours": {
                "$map": {
                    "input": [
                         00,01,02,03,04,05,06,07,08,09,10,11,
                         12,13,14,15,16,17,18,19,20,21,22,23
                     ],
                     "as": "el",
                     "in": {
                          "$ifNull": [
                              { "$arrayElemAt": [
                                  { "$map": {
                                      "input": { "$filter": {
                                         "input": "$temp_hours",
                                         "as": "tmp",
                                         "cond": {
                                             "$eq": [ "$$el", "$$tmp.index" ]
                                         }
                                      }},
                                     "as": "out",
                                     "in": "$$out.count"
                                  }},
                                  0
                              ]},
                              0
                          ]
                     }
                }
            }
         }},
    
         // Optionally sort in "uid" order
         { "$sort": { "_id": 1 } }
     ])
    

    以前のMongoDB3.2では、1日のすべての時間の配列コンテンツをマップするために、もう少し関与する必要があります。

    db.collection.aggregate([
    
        // First group by hour within "uid" and keep distinct "to" and "from"
        { "$group": {
            "_id": {
                "uid": "$uid",
                "time": { "$hour": "$timestamp" }
            },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "count": { "$sum": 1 }
        }},
    
        // Roll-up to "uid" and keep each hour in an array
        { "$group": {
            "_id": "$_id.uid",
            "total": { "$sum": "$count" },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "temp_hours": { 
                "$push": {
                    "index": "$_id.time",
                    "count": "$count"
                }
            }
         }},
    
         // Getting distinct "to" and "from" requires a double unwind of arrays
         { "$unwind": "$to" },
         { "$unwind": "$to" },
         { "$unwind": "$from" },
         { "$unwind": "$from" },
    
         // And then adding back to sets for distinct, also adding the indexes array
         { "$group": {
            "_id": "$_id",
            "total": { "$first": "$total" },
            "from": { "$addToSet": "$from" },
            "to": { "$addToSet": "$to" },
            "temp_hours": { "$first": "$temp_hours" },
            "indexes": { "$first": { "$literal": [
                         00,01,02,03,04,05,06,07,08,09,10,11,
                         12,13,14,15,16,17,18,19,20,21,22,23
            ] } }
         }},
    
         // Denormalize both arrays
         { "$unwind": "$temp_hours" },
         { "$unwind": "$indexes" },
    
         // Marry up the index entries and keep either the value or 0
         // Note you are normalizing the double unwind to distinct index
         { "$group": {
             "_id": {
                 "_id": "$_id",
                 "index": "$indexes"
             },
             "total": { "$first": "$total" }, 
             "from": { "$first": "$from" },
             "to": { "$first": "$to" },
             "count": {
                 "$max": {
                     "$cond": [
                         { "$eq": [ "$indexes", "$temp_hours.index" ] },
                         "$temp_hours.count",
                         0
                     ]
                 }
             }
         }},
    
         // Sort to keep index order - !!Important!!         
         { "$sort": { "_id": 1 } },
    
         // Put the hours into the array and get sizes for other results
         { "$group": {
             "_id": "$_id._id",
             "count": { "$first": "$total" },
             "from_count": { "$first": { "$size": "$from" } },
             "to_count": { "$first": { "$size": "$to" } },
             "hours": { "$push": "$count" }
         }},
    
         // Optionally sort in "uid" order
         { "$sort": { "_id": 1 } }
    ])
    

    これを分解するために、ここでの両方のアプローチは同じ基本的な手順に従いますが、24時間の「時間」のマッピングで発生する唯一の実際の違いがあります。

    最初の集計では、 $ group ステージでは、目的は、データに存在する1時間あたりの結果と、各「uid」値を取得することです。 $ hourの単純な日付集計演算子 グループ化キーの一部としてこの値を取得するのに役立ちます。

    $ addToSet 操作はそれ自体が一種の「ミニグループ」であり、これにより、基本的に1時間ごとにグループ化しながら、「to」と「from」の各値の「個別のセット」を維持できます。

    次の$group すべてのデータをロールアップして「uid」ごとにグループ化する間、1時間ごとに記録された「カウント」が配列に保持されるため、より「組織的」です。これにより、基本的に、結果に本当に必要なすべての「データ」が得られますが、もちろん $ addToSet ここでの操作は、1時間ごとに決定される個別のセットの「配列内の配列」を追加するだけです。

    これらの値を「uid」ごとに真に異なるリストとして取得するには、 $ unwind そして最後に、別個の「セット」としてグループ化します。同じ$addToSet これを圧縮し、 $ first 操作は、他のフィールドの「最初の」値を取得するだけです。これらの値は、ターゲットの「uidごとの」データに対してすでにすべて同じです。私たちはそれらに満足しているので、そのままにしておいてください。

    ここでの最終段階は本質的に「見た目」であり、クライアント側のコードでも同様に達成できます。 1時間間隔ごとにデータが存在するわけではないため、各時間を表す値の配列にマップする必要があります。ここでの2つのアプローチは、バージョン間で使用可能な演算子の機能によって異なります。

    MongoDB 3.2リリースには、 $ filter> および $ arrayElemAt 可能なすべてのインデックス位置(24時間)の入力ソースを、使用可能なデータ内のそれらの時間のカウントに対してすでに決定されている値に「転置」するロジックを効果的に作成できる演算子。これは基本的に、利用可能な時間ごとにすでに記録されている値の「直接ルックアップ」であり、存在するかどうかを確認します。ここで、カウントは完全な配列に置き換えられます。存在しない場合、デフォルト値は 0 その場で使用されます。

    これらの演算子がない場合、この「一致」を行うことは、本質的に、比較および転置するために、両方の配列(記録されたデータと完全な24の位置)を非正規化することを意味します。これは、「インデックス」値を単純に比較してその時間の結果があったかどうかを確認する2番目のアプローチで起こっていることです。 $ max ここでの演算子は、主に2つの $ unwindのために使用されます ステートメント。ソースデータから記録された各値は、すべての可能なインデックス位置に対して再現されます。これは、「インデックス時間」ごとに必要な値だけに「コンパクト」になります。

    後者のアプローチでは、 $ sort グループ化について_id 価値。これは、「インデックス」の位置が含まれているためです。これは、このコンテンツを順序付けが必要な配列に戻すときに必要になります。もちろん、これが最後の $ group ここで、順序付けられた位置が $ pushを使用して配列に配置されるステージ 。

    「個別のリスト」に戻ると、 $ size 演算子はすべての場合に使用され、「to」と「from」のリスト内の個別の値の「長さ」、つまり「カウント」を決定します。これは、少なくともMongoDB 2.6の唯一の実際の制約ですが、それ以外の場合は、各配列を個別に「アンワインド」してから、 _idにグループ化することで置き換えることができます。 各セットの配列エントリをカウントするためにすでに存在しています。これは基本的なプロセスですが、 $ sizeが表示されるはずです。 ここでは、全体的なパフォーマンスを上げるには、演算子の方が適しています。

    最後に、結論データは少しずれています。おそらく、「from」に「ddd」が含まれるエントリは、「to」でも同じであることが意図されていたため、代わりに「bbb」として記録されます。これにより、「to」の3番目の「uid」グループの個別のカウントが1エントリ減少します。しかしもちろん、ソースデータが与えられた場合の論理的な結果は健全です:

    { "_id" : 1000000, "count" : 3, "from_count" : 2, "to_count" : 2, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
    { "_id" : 2000000, "count" : 2, "from_count" : 1, "to_count" : 1, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
    { "_id" : 3000000, "count" : 5, "from_count" : 5, "to_count" : 4, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0 ] }
    

    N.Bソースにもタイプミスがあり、区切り文字がに挿入されています すべての行のタイムスタンプの直後のコンマの代わりに。




    1. 注文の最上位または最初のレコードのSpringjavaMongoDB@Queryアノテーション

    2. symfony2で「like」という条件でmongodbをクエリする方法

    3. 流星のMongoDBデータベースはどこにありますか?

    4. mongoで条件付きTTLを作成します