MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Групирайте и пребройте в начален и краен диапазон

Алгоритъмът за това е основно да се "итерират" стойности между интервала на двете стойности. MongoDB има няколко начина да се справи с това, което винаги е присъствало с mapReduce() и с нови функции, достъпни за aggregate() метод.

Ще разширя избора ви, за да покажа съзнателно припокриващ се месец, тъй като вашите примери нямаха такъв. Това ще доведе до показване на стойностите на „HGV“ в „три“ месеца на извеждане.

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

Агрегат – Изисква 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" }
    }
  }}
])

Ключът за това да работи е $range оператор, който приема стойности за "начало" и "край", както и "интервал", за да се приложи. Резултатът е масив от стойности, взети от "началото" и увеличени до достигане на "края".

Използваме това с startDate и крайна дата за генериране на възможните дати между тези стойности. Ще забележите, че трябва да направим малко математика тук, тъй като $range взема само 32-битово цяло число, но можем да отнеме милисекундите от стойностите на времевия печат, така че това е добре.

Тъй като искаме „месеци“, приложените операции извличат стойностите за месеца и годината от генерирания диапазон. Ние всъщност генерираме диапазона като „дни“ между тях, тъй като „месеците“ са трудни за работа в математиката. Следващият $reduce операцията отнема само "отделните месеци" от периода от време.

Следователно резултатът от първия етап на тръбопровода за агрегиране е ново поле в документа, което е „масив“ от всички отделни месеци, обхванати между startDate и крайна дата . Това дава "итератор" за останалата част от операцията.

Под „итератор“ имам предвид, отколкото когато прилагаме $unwind получаваме копие на оригиналния документ за всеки отделен месец, обхванат от интервала. След това това позволява следните две $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 израз в акумулатора, за да видите дали текущите данни са в рамките на генерирания групиран диапазон. Освен това, тъй като генерирането е „външно“, наистина не се нуждаем от оператора MongoDB 3.4 на $range , така че това може да се приложи и към по-ранни версии:

// 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 и простият случай на "iterator", както беше споменато по-горе, се обработва от 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
}

Така че няма второ групиране, което да се комбинира с масиви, но ние произведохме същия основен обобщен изход.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как да затворите курсора в MongoKit

  2. как да покажете заявка, докато използвате анотации на заявка с MongoRepository с пролетни данни

  3. Търсене на пълен текст с тегло в мангуста

  4. Метод или заявка MongoTemplate за намиране на максимални стойности от полета

  5. MongoDB Изключение:Сървърът отчита кабелна версия 0, но версията на libmongoc изисква поне 3