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

Как да изчислим текущата сума с помощта на агрегат?

Всъщност е по-подходящ за mapReduce от рамката за агрегиране, поне при първоначалното решаване на проблеми. Рамката за агрегиране няма концепция за стойността на предишен документ или предишната „групирана“ стойност на документ, така че ето защо не може да направи това.

От друга страна, mapReduce има "глобален обхват", който може да се споделя между етапите и документите, докато се обработват. Това ще ви осигури "текущата сума" за текущото салдо в края на деня, който ви е необходим.

db.collection.mapReduce(
  function () {
    var date = new Date(this.dateEntry.valueOf() -
      ( this.dateEntry.valueOf() % ( 1000 * 60 * 60 * 24 ) )
    );

    emit( date, this.amount );
  },
  function(key,values) {
      return Array.sum( values );
  },
  { 
      "scope": { "total": 0 },
      "finalize": function(key,value) {
          total += value;
          return total;
      },
      "out": { "inline": 1 }
  }
)      

Това ще сумира по групиране по дати и след това в секцията „финализиране“ прави кумулативна сума за всеки ден.

   "results" : [
            {
                    "_id" : ISODate("2015-01-06T00:00:00Z"),
                    "value" : 50
            },
            {
                    "_id" : ISODate("2015-01-07T00:00:00Z"),
                    "value" : 150
            },
            {
                    "_id" : ISODate("2015-01-09T00:00:00Z"),
                    "value" : 179
            }
    ],

В дългосрочен план би било най-добре да имате отделна колекция с запис за всеки ден и да промените баланса с помощта на $inc в актуализация. Просто направете и $inc upsert в началото на всеки ден, за да създадете нов документ, пренасящ баланса от предходния ден:

// increase balance
db.daily(
    { "dateEntry": currentDate },
    { "$inc": { "balance": amount } },
    { "upsert": true }
);

// decrease balance
db.daily(
    { "dateEntry": currentDate },
    { "$inc": { "balance": -amount } },
    { "upsert": true }
);

// Each day
var lastDay = db.daily.findOne({ "dateEntry": lastDate });
db.daily(
    { "dateEntry": currentDate },
    { "$inc": { "balance": lastDay.balance } },
    { "upsert": true }
);

Как да НЕ правите това

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

Прилага се същото основно правило, че рамката за агрегиране не може препраща към стойност от предишен "документ", нито може да съхранява "глобална променлива". „Хакване“ това чрез принуда на всички резултати в масив:

db.collection.aggregate([
  { "$group": {
    "_id": { 
      "y": { "$year": "$dateEntry" }, 
      "m": { "$month": "$dateEntry" }, 
      "d": { "$dayOfMonth": "$dateEntry" } 
    }, 
    "amount": { "$sum": "$amount" }
  }},
  { "$sort": { "_id": 1 } },
  { "$group": {
    "_id": null,
    "docs": { "$push": "$$ROOT" }
  }},
  { "$addFields": {
    "docs": {
      "$map": {
        "input": { "$range": [ 0, { "$size": "$docs" } ] },
        "in": {
          "$mergeObjects": [
            { "$arrayElemAt": [ "$docs", "$$this" ] },
            { "amount": { 
              "$sum": { 
                "$slice": [ "$docs.amount", 0, { "$add": [ "$$this", 1 ] } ]
              }
            }}
          ]
        }
      }
    }
  }},
  { "$unwind": "$docs" },
  { "$replaceRoot": { "newRoot": "$docs" } }
])

Това не е нито ефективно решение, нито „безопасно“ като се има предвид, че по-големите набори от резултати имат много реалната вероятност за нарушаване на ограничението от 16MB BSON. Като „златно правило“ , всичко, което предлага да се постави ВСЯКО съдържание в масива на един документ:

{ "$group": {
  "_id": null,
  "docs": { "$push": "$$ROOT" }
}}

тогава това е основен недостатък и следователно не решение .

Заключение

Далеч по-убедителните начини за справяне с това обикновено са последваща обработка на текущия курсор на резултатите:

var globalAmount = 0;

db.collection.aggregate([
  { $group: {
    "_id": { 
      y: { $year:"$dateEntry"}, 
      m: { $month:"$dateEntry"}, 
      d: { $dayOfMonth:"$dateEntry"} 
    }, 
    amount: { "$sum": "$amount" }
  }},
  { "$sort": { "_id": 1 } }
]).map(doc => {
  globalAmount += doc.amount;
  return Object.assign(doc, { amount: globalAmount });
})

Така че като цяло винаги е по-добре да:

  • Използвайте итерация на курсора и променлива за проследяване за суми. mapReduce пробата е измислен пример за опростения процес по-горе.

  • Използвайте предварително обобщени суми. Вероятно в съответствие с итерация на курсора в зависимост от вашия процес на предварително агрегиране, независимо дали това е просто общ общ интервал или „пренесен напред“ текущ сбор.

Рамката за агрегиране наистина трябва да се използва за "агрегиране" и нищо повече. Принуждаването на принудителни действия върху данни чрез процеси като манипулиране в масив само за обработка, както искате, не е нито разумно, нито безопасно и най-важното е, че кодът за манипулиране на клиента е далеч по-чист и по-ефективен.

Оставете базите данни да правят нещата, в които са добри, тъй като вашите "манипулации" се обработват много по-добре в код.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Meteor.js се разгръща в example.com или www.example.com?

  2. Скорост на MongoDB {aggregation $match} срещу {find}

  3. mongo групова заявка как да запазите полета

  4. Как да предавате поточно резултатите от заявките на MongoDB с nodejs?

  5. MongoDB актуализиране Много()