MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Grupuj i licz w zakresie początkowym i końcowym

Algorytm do tego polega w zasadzie na „iterowaniu” wartości między przedziałem dwóch wartości. MongoDB ma kilka sposobów radzenia sobie z tym, będąc tym, co zawsze było obecne w mapReduce() oraz z nowymi funkcjami dostępnymi dla aggregate() metoda.

Rozszerzę twój wybór, aby celowo pokazać nakładający się miesiąc, ponieważ w twoich przykładach go nie było. Spowoduje to pojawienie się wartości „ciężarowych” w ciągu „trzech” miesięcy produkcji.

{
        "_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"
}

Agregacja — wymaga MongoDB 3.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" }
    }
  }}
])

Kluczem do tego, aby to zadziałało, jest $range operator, który przyjmuje wartości dla "początku" i "końca" oraz "przedziału" do zastosowania. Wynikiem jest tablica wartości pobranych od „początku” i inkrementowanych aż do osiągnięcia „końca”.

Używamy tego z startDate i endDate aby wygenerować możliwe daty pomiędzy tymi wartościami. Zauważysz, że musimy tutaj trochę policzyć, ponieważ $zakres zajmuje tylko 32-bitową liczbę całkowitą, ale możemy odjąć milisekundy od wartości znacznika czasu, więc jest to w porządku.

Ponieważ chcemy "miesięcy", zastosowane operacje wyodrębniają wartości miesiąca i roku z wygenerowanego zakresu. W rzeczywistości generujemy zakres jako „dni” pomiędzy nimi, ponieważ „miesiące” są trudne w matematyce. Kolejne $reduce operacja zajmuje tylko "różne miesiące" z zakresu dat.

Dlatego wynikiem pierwszego etapu potoku agregacji jest nowe pole w dokumencie, które jest "tablicą" wszystkich odrębnych miesięcy zawartych między startDate i endDate . Daje to "iterator" do końca operacji.

Przez „iterator” mam na myśli niż wtedy, gdy stosujemy $unwind otrzymujemy kopię oryginalnego dokumentu za każdy odrębny miesiąc objęty przedziałem. To umożliwia następnie następujące dwa $group etapy, aby najpierw zastosować grupowanie do wspólnego klucza „miesiąc” i „typ” w celu „sumowania” zliczeń za pomocą $sum , a następnie $group sprawia, że ​​klucz jest po prostu „typem” i umieszcza wyniki w tablicy poprzez $push .

Daje to wynik z powyższych danych:

{
        "_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
                }
        ]
}

Należy zauważyć, że zakres „miesięcy” występuje tylko tam, gdzie istnieją rzeczywiste dane. Chociaż możliwe jest uzyskanie wartości zerowych w całym zakresie, wymaga to sporo wysiłku i nie jest zbyt praktyczne. Jeśli chcesz uzyskać wartości zerowe, lepiej dodać to w przetwarzaniu końcowym w kliencie po pobraniu wyników.

Jeśli naprawdę masz serce ustawione na wartości zerowe, powinieneś osobno zapytać o $min i $max wartości i przekaż je, aby potok „brute force” wygenerował kopie dla każdej podanej możliwej wartości zakresu.

Więc tym razem „zakres” jest tworzony zewnętrznie dla wszystkich dokumentów, a następnie używasz $war do akumulatora, aby sprawdzić, czy bieżące dane mieszczą się w zgrupowanym zakresie. Również ponieważ generacja jest "zewnętrzna", tak naprawdę nie potrzebujemy operatora MongoDB 3.4 $range , więc można to zastosować również we wcześniejszych wersjach:

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

Daje to spójne zerowe wypełnienia we wszystkich możliwych miesiącach we wszystkich grupach:

{
        "_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

Wszystkie wersje MongoDB obsługują mapReduce, a prosty przypadek „iteratora”, jak wspomniano powyżej, jest obsługiwany przez for pętla w programie mapującym. Możemy uzyskać dane wyjściowe jako wygenerowane aż do pierwszej grupy $ z góry, po prostu wykonując:

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

Co daje:

{
        "_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
}

Nie ma więc drugiego grupowania do łączenia w tablice, ale stworzyliśmy te same podstawowe zagregowane dane wyjściowe.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Jak mogę wybrać liczbę rekordów na określone pole za pomocą mongodb?

  2. Sterownik C# mongodb 2.0 - Jak upsert w operacji zbiorczej?

  3. Jak zmienić kolejność tablicy za pomocą MongoDB?

  4. Jak ustawić opcje serializacji dla wartości geograficznych przy użyciu oficjalnego sterownika C# 10gen?

  5. Importuj plik CSV do MongoDB za pomocą mongoimport