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

開始範囲と終了範囲をグループ化してカウントします

    このためのアルゴリズムは、基本的に2つの値の間隔の間で値を「反復」することです。 MongoDBには、これに対処するためのいくつかの方法があります。これは、 mapReduce() また、 Aggregate()<で利用できる新機能を備えています。 / code> メソッド。

    あなたの例には月がなかったので、意図的に重複する月を表示するために、選択を拡張します。これにより、「HGV」値が「3」か月の出力に表示されます。

    {
            "_id" : 1,
            "startDate" : ISODate("2017-01-01T00:00:00Z"),
            "endDate" : ISODate("2017-02-25T00:00:00Z"),
            "type" : "CAR"
    }
    {
            "_id" : 2,
            "startDate" : ISODate("2017-02-17T00:00:00Z"),
            "endDate" : ISODate("2017-03-22T00:00:00Z"),
            "type" : "HGV"
    }
    {
            "_id" : 3,
            "startDate" : ISODate("2017-02-17T00:00:00Z"),
            "endDate" : ISODate("2017-04-22T00:00:00Z"),
            "type" : "HGV"
    }
    

    Aggregate-MongoDB3.4が必要

    db.cars.aggregate([
      { "$addFields": {
        "range": {
          "$reduce": {
            "input": { "$map": {
              "input": { "$range": [ 
                { "$trunc": { 
                  "$divide": [ 
                    { "$subtract": [ "$startDate", new Date(0) ] },
                    1000
                  ]
                }},
                { "$trunc": {
                  "$divide": [
                    { "$subtract": [ "$endDate", new Date(0) ] },
                    1000
                  ]
                }},
                60 * 60 * 24
              ]},
              "as": "el",
              "in": {
                "$let": {
                  "vars": {
                    "date": {
                      "$add": [ 
                        { "$multiply": [ "$$el", 1000 ] },
                        new Date(0)
                      ]
                    },
                    "month": {
                    }
                  },
                  "in": {
                    "$add": [
                      { "$multiply": [ { "$year": "$$date" }, 100 ] },
                      { "$month": "$$date" }
                    ]
                  }
                }
              }
            }},
            "initialValue": [],
            "in": {
              "$cond": {
                "if": { "$in": [ "$$this", "$$value" ] },
                "then": "$$value",
                "else": { "$concatArrays": [ "$$value", ["$$this"] ] }
              }
            }
          }
        }
      }},
      { "$unwind": "$range" },
      { "$group": {
        "_id": {
          "type": "$type",
          "month": "$range"
        },
        "count": { "$sum": 1 }
      }},
      { "$sort": { "_id": 1 } },
      { "$group": {
        "_id": "$_id.type",
        "monthCounts": { 
          "$push": { "month": "$_id.month", "count": "$count" }
        }
      }}
    ])
    

    この作業を行うための鍵は、 $ range> 適用する「開始」と「終了」、および「間隔」の値をとる演算子。結果は、「開始」から取得され、「終了」に達するまでインクリメントされる値の配列です。

    これをstartDateで使用します およびendDate それらの値の間に可能な日付を生成します。 $ range なので、ここでいくつかの計算を行う必要があることに注意してください。 32ビット整数のみを取りますが、タイムスタンプ値からミリ秒を取り除くことができるので、問題ありません。

    「月」が必要なため、適用される操作は、生成された範囲から月と年の値を抽出します。 「月」は数学では扱いにくいため、実際にはその間の「日」として範囲を生成します。後続の $ reduce 操作は、日付範囲から「明確な月」のみを取ります。

    したがって、最初の集計パイプラインステージの結果は、ドキュメント内の新しいフィールドになります。これは、 startDateの間にカバーされるすべての個別の月の「配列」です。 およびendDate 。これにより、残りの操作の「イテレータ」が提供されます。

    「イテレータ」とは、 $unwind<を適用する場合よりも意味します。 / code> 間隔でカバーされる個別の月ごとに、元のドキュメントのコピーを取得します。これにより、次の2つの $ groupが許可されます。 $ sum 、次の $ group キーを単なる「タイプ」にし、結果をを介して配列に入れます。 $ push

    これにより、上記のデータの結果が得られます:

    {
            "_id" : "HGV",
            "monthCounts" : [
                    {
                            "month" : 201702,
                            "count" : 2
                    },
                    {
                            "month" : 201703,
                            "count" : 2
                    },
                    {
                            "month" : 201704,
                            "count" : 1
                    }
            ]
    }
    {
            "_id" : "CAR",
            "monthCounts" : [
                    {
                            "month" : 201701,
                            "count" : 1
                    },
                    {
                            "month" : 201702,
                            "count" : 1
                    }
            ]
    }
    

    「月」の範囲は、実際のデータがある場合にのみ存在することに注意してください。ある範囲でゼロ値を生成することは可能ですが、それを行うにはかなりのラングリングが必要であり、あまり実用的ではありません。ゼロ値が必要な場合は、結果が取得されたら、クライアントで後処理に値を追加することをお勧めします。

    本当にゼロ値に心を向けている場合は、個別に $ min および $ max 値を渡し、パイプラインを「ブルートフォース」して、提供された可能な範囲値ごとにコピーを生成します。

    したがって、今回はすべてのドキュメントの外部で「範囲」を作成し、 $ cond 現在のデータが生成されたグループ化された範囲内にあるかどうかを確認するために、アキュムレータにステートメントを入力します。また、生成は「外部」であるため、 $ rangeのMongoDB3.4演算子は実際には必要ありません。 、したがって、これは以前のバージョンにも適用できます:

    // Get min and max separately 
    var ranges = db.cars.aggregate(
     { "$group": {
       "_id": null,
       "startRange": { "$min": "$startDate" },
       "endRange": { "$max": "$endDate" }
     }}
    ).toArray()[0]
    
    // Make the range array externally from all possible values
    var range = [];
    for ( var d = new Date(ranges.startRange.valueOf()); d <= ranges.endRange; d.setUTCMonth(d.getUTCMonth()+1)) {
      var v = ( d.getUTCFullYear() * 100 ) + d.getUTCMonth()+1;
      range.push(v);
    }
    
    // Run conditional aggregation
    db.cars.aggregate([
      { "$addFields": { "range": range } },
      { "$unwind": "$range" },
      { "$group": {
        "_id": {
          "type": "$type",
          "month": "$range"
        },
        "count": { 
          "$sum": {
            "$cond": {
              "if": {
                "$and": [
                  { "$gte": [
                    "$range",
                    { "$add": [
                      { "$multiply": [ { "$year": "$startDate" }, 100 ] },
                      { "$month": "$startDate" }
                    ]}
                  ]},
                  { "$lte": [
                    "$range",
                    { "$add": [
                      { "$multiply": [ { "$year": "$endDate" }, 100 ] },
                      { "$month": "$endDate" }
                    ]}
                  ]}
                ]
              },
              "then": 1,
              "else": 0
            }
          }
        }
      }},
      { "$sort": { "_id": 1 } },
      { "$group": {
        "_id": "$_id.type",
        "monthCounts": { 
          "$push": { "month": "$_id.month", "count": "$count" }
        }
      }}
    ])
    

    これにより、すべてのグループで可能なすべての月に一貫したゼロフィルが生成されます:

    {
            "_id" : "HGV",
            "monthCounts" : [
                    {
                            "month" : 201701,
                            "count" : 0
                    },
                    {
                            "month" : 201702,
                            "count" : 2
                    },
                    {
                            "month" : 201703,
                            "count" : 2
                    },
                    {
                            "month" : 201704,
                            "count" : 1
                    }
            ]
    }
    {
            "_id" : "CAR",
            "monthCounts" : [
                    {
                            "month" : 201701,
                            "count" : 1
                    },
                    {
                            "month" : 201702,
                            "count" : 1
                    },
                    {
                            "month" : 201703,
                            "count" : 0
                    },
                    {
                            "month" : 201704,
                            "count" : 0
                    }
            ]
    }
    

    MapReduce

    MongoDBのすべてのバージョンはmapReduceをサポートしており、上記の「イテレーター」の単純なケースは forによって処理されます。 マッパーでループします。最初の$groupまで生成された出力を取得できます 上から:

    db.cars.mapReduce(
      function () {
        for ( var d = this.startDate; d <= this.endDate;
          d.setUTCMonth(d.getUTCMonth()+1) )
        { 
          var m = new Date(0);
          m.setUTCFullYear(d.getUTCFullYear());
          m.setUTCMonth(d.getUTCMonth());
          emit({ id: this.type, date: m},1);
        }
      },
      function(key,values) {
        return Array.sum(values);
      },
      { "out": { "inline": 1 } }
    )
    

    生成するもの:

    {
            "_id" : {
                    "id" : "CAR",
                    "date" : ISODate("2017-01-01T00:00:00Z")
            },
            "value" : 1
    },
    {
            "_id" : {
                    "id" : "CAR",
                    "date" : ISODate("2017-02-01T00:00:00Z")
            },
            "value" : 1
    },
    {
            "_id" : {
                    "id" : "HGV",
                    "date" : ISODate("2017-02-01T00:00:00Z")
            },
            "value" : 2
    },
    {
            "_id" : {
                    "id" : "HGV",
                    "date" : ISODate("2017-03-01T00:00:00Z")
            },
            "value" : 2
    },
    {
            "_id" : {
                    "id" : "HGV",
                    "date" : ISODate("2017-04-01T00:00:00Z")
            },
            "value" : 1
    }
    

    したがって、配列に複合する2番目のグループ化はありませんが、同じ基本的な集約出力を生成しました。




    1. Ubuntu 16.04/18.04へのRedisのインストール

    2. CodeIgniterにRedisを実装する方法は?

    3. 1つのコマンドでmongoDBを停止する方法

    4. SpringDataMongoDB基準を使用して動的クエリを構築する