MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Làm thế nào để tính toán tổng số đang chạy bằng cách sử dụng tổng hợp?

Trên thực tế, phù hợp hơn với mapReduce hơn là khung tổng hợp, ít nhất là trong việc giải quyết vấn đề ban đầu. Khung tổng hợp không có khái niệm về giá trị của tài liệu trước đó hoặc giá trị "được nhóm" trước đó của tài liệu, vì vậy đây là lý do tại sao nó không thể thực hiện điều này.

Mặt khác, mapReduce có "phạm vi toàn cầu" có thể được chia sẻ giữa các giai đoạn và tài liệu khi chúng được xử lý. Điều này sẽ giúp bạn nhận được "tổng số tiền đang hoạt động" cho số dư hiện tại vào cuối ngày mà bạn yêu cầu.

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

Điều đó sẽ tổng hợp theo nhóm ngày và sau đó trong phần "tổng kết", nó tạo ra tổng tích lũy từ mỗi ngày.

   "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
            }
    ],

Về lâu dài, tốt nhất bạn nên có một bộ sưu tập riêng với mục nhập cho mỗi ngày để thay đổi số dư bằng cách sử dụng $inc trong một bản cập nhật. Cũng chỉ cần thực hiện một $inc nâng cấp vào đầu mỗi ngày để tạo tài liệu mới chuyển tiếp số dư từ ngày hôm trước:

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

Cách KHÔNG làm điều này

Mặc dù đúng là vì văn bản gốc có nhiều toán tử hơn được đưa vào khung tổng hợp, nhưng điều đang được hỏi ở đây vẫn không thực tế để thực hiện trong một câu lệnh tổng hợp.

Quy tắc cơ bản tương tự được áp dụng mà khuôn khổ tổng hợp không thể tham chiếu một giá trị từ "tài liệu" trước đó, cũng như không thể lưu trữ một "biến toàn cục". "Lấy cắp dữ liệu" điều này bằng cách ép buộc tất cả các kết quả vào một mảng:

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

Đó không phải là giải pháp hiệu quả hoặc "an toàn" xem xét rằng các bộ kết quả lớn hơn có xác suất rất thực là vi phạm giới hạn 16MB BSON. Như một "quy tắc vàng" , bất kỳ thứ gì đề xuất đặt TẤT CẢ nội dung trong mảng của một tài liệu:

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

thì đó là một lỗ hổng cơ bản và do đó không phải là một giải pháp .

Kết luận

Các cách thuyết phục hơn nhiều để xử lý vấn đề này thường là xử lý bài đăng trên con trỏ kết quả đang chạy:

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

Vì vậy, nói chung, tốt hơn hết là:

  • Sử dụng lặp lại con trỏ và một biến theo dõi cho các tổng. mapReduce mẫu là một ví dụ giả định về quy trình đơn giản hóa ở trên.

  • Sử dụng các tổng được tổng hợp trước. Có thể kết hợp với lặp lại con trỏ tùy thuộc vào quá trình tổng hợp trước của bạn, cho dù đó chỉ là tổng khoảng thời gian hay tổng số đang chạy "chuyển tiếp".

Khung tổng hợp thực sự nên được sử dụng để "tổng hợp" và không có gì hơn. Việc ép buộc đối với dữ liệu thông qua các quy trình như thao tác vào một mảng chỉ để xử lý theo cách bạn muốn là không khôn ngoan hay an toàn và quan trọng nhất là mã thao tác của ứng dụng khách sạch hơn và hiệu quả hơn nhiều.

Hãy để cơ sở dữ liệu làm những việc mà chúng giỏi, vì thay vào đó, các "thao tác" của bạn được xử lý tốt hơn nhiều trong mã.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Nhập kiểu dữ liệu ngày bằng mongoimport

  2. Cách sử dụng mã hóa để bảo vệ dữ liệu MongoDB của bạn

  3. Điều gì đang xảy ra với Meteor và Fibers / bindEnosystem ()?

  4. Kiểm tra xem một trường có chứa một chuỗi hay không

  5. Cuộc chiến của các cơ sở dữ liệu NoSQL - So sánh MongoDB và CouchDB