2012-10-20 56 views
5

Tôi có một bố trí db đơn giản như thế này:Tính đếm và trung bình với MongoDB hợp

client 
    id 
    sex (male/female) 
    birthday (date)  

client 
    id 
    sex (male/female) 
    birthday (date) 

(...) 

Tôi đang cố gắng để viết một lệnh tập hợp mà kết quả đầu ra có bao nhiêu nam và khách hàng nữ Tôi đã có, và tôi Tôi cũng muốn sản xuất tuổi trung bình của nam và nữ, không chắc tôi có thể làm điều này trong cùng một lệnh hoặc tôi cần 2 riêng biệt?

// Count of males/females, average age 
Clients.aggregate({ 
    $project : {"sex"  : 1, 
      "sexCount" : 1, 
      "birthday" : 1, 
      "avgAge" : 1 
       } 
    }, 
    { 
     $match: {"sex": {$exists: true}} 
    }, 
    { 
     $group: { 
        _id  : "$sex", 
      sexCount : { $sum: 1 }, 
      avgAge : { $avg: "$birthday" }, 
      } 
    }, 
    { $sort: { _id: 1 } } 
    , function(err, sex_dbres) { 
      if (err) 
       throw err; 
      else{ 
       (...) 
      } 
     });   

Với mã ở trên, tôi nhận được số lượng nam/nữ, nhưng avgAge là 0. Bất kỳ ý tưởng nào?

Rất cám ơn

+0

lưu ý rằng bạn không cần phải chiếu các trường sexCount hoặc avgAge trong bước đầu tiên vì đó là các trường bạn sẽ tính toán trong bước nhóm $. –

Trả lời

4

Đối tượng ngày không được "trung bình", nhưng con số có thể. Bạn có thể chuyển đổi ngày của bạn thành giá trị dấu thời gian, và sau đó tìm trung bình từ nó. Nhưng vẫn không phải là độ tuổi trung bình, bạn sẽ cần phải trừ kết quả từ ngày hiện tại bên ngoài hàm tổng hợp.

Một tùy chọn khác là giả định rằng tuổi có thể được tính chỉ sử dụng phần năm của ngày (tức là, nếu tôi sinh ngày 1 tháng 12 năm 2000, trong báo cáo hôm nay tôi sẽ 12 tuổi, chứ không phải 11). Trong trường hợp này, bạn có thể sử dụng date operators để trích xuất giá trị năm.

$project : {"sex"  : 1, 
      "sexCount" : 1, 
      "year" : {$year: "$birthday"}, 
      } 
}, 
$project : {"sex"  : 1, 
      "sexCount" : 1, 
      "age" : {$subtract: [2012, '$year']}, 
      } 
}, 
+0

Cảm ơn. Chỉ cần nhận ra rằng sinh nhật được lưu trữ như là chuỗi ("Sat 22 tháng 5 năm 1982 00:00:00 GMT + 0200") mà làm cho mọi thứ khó khăn hơn một chút. Có thể đúc nó thành một số không? Tôi đã cố gắng làm một chất nền để có được chỉ là một phần năm nhưng sau đó tôi đang gặp rắc rối biến đó thành một số để sau đó làm $ trừ bạn đề xuất. Nếu điều này là khó khăn thì tôi đoán tôi có thể biến trường thành một ngày. –

+0

Chuyển đổi không phải là một phần của khung kết hợp, tôi đoán bạn sẽ cần phải sử dụng MapReduce, nơi bạn có thể viết mã javascript tùy ý hoặc chạy qua cơ sở dữ liệu của mình và chuyển đổi tất cả các ngày. – Dmitry

+0

Cảm ơn bạn! Tôi trích xuất năm vào một lĩnh vực riêng biệt và bây giờ có thể dễ dàng làm trung bình. –

6

Câu trả lời sẽ đơn giản hơn nhiều nếu bạn được lưu trữ tuổi trong văn bản gốc (như Dmitry đăng, bạn chỉ có thể làm một thẳng avgAge:{$avg:"$age"} trong bước $group của bạn.

Aggregation Framework là khá tiện lợi và mặc dù có nhiều nhà khai thác mát mẻ cho phép bạn tính toán lĩnh vực này tuổi mất tích "on the fly"

tôi sẽ để lưu trữ từng bước của tập hợp trong một biến vì vậy nó dễ dàng hơn để xem những gì đang xảy ra:.

today = new Date(); 
// split today and bday into numerical year and numerical day-of-the-year 
project1= { 
    "$project" : { 
     "sex" : 1, 
     "todayYear" : { 
      "$year" : today 
     }, 
     "todayDay" : { 
      "$dayOfYear" : today 
     }, 
     "by" : { 
      "$year" : "$bday" 
     }, 
     "bd" : { 
      "$dayOfYear" : "$bday" 
     } 
    } 
}; 
// calculate age in days by subtracting bday in days from today in days 
project2 = { 
    "$project" : { 
     "sex" : 1, 
     "age" : { 
      "$subtract" : [ 
       { 
        "$add" : [ 
         { 
          "$multiply" : [ 
           "$todayYear", 
           365 
          ] 
         }, 
         "$todayDay" 
        ] 
       }, 
       { 
        "$add" : [ 
         { 
          "$multiply" : [ 
           "$by", 
           365 
          ] 
         }, 
         "$bd" 
        ] 
       } 
      ] 
     } 
    } 
}; 
// sum up for each sex the count and compute avg age (in days) 
group = { 
    "$group" : { 
     "_id" : "$sex", 
     "total" : { 
      "$sum" : 1 
     }, 
     "avgAge" : { 
      "$avg" : "$age" 
     } 
    } 
}; 
// divide days by 365 to get age in years. 
project3 = { 
    "$project" : { 
     "_id" : 0, 
     "sex" : "$_id", 
     "total" : 1, 
     "averageAge" : { 
      "$divide" : [ 
       "$avgAge", 
       365 
      ] 
     } 
    } 
}; 

Bây giờ bạn có thể chạy các tập hợp:

> db.client.find({},{_id:0}) 
{ "sex" : "male", "bday" : ISODate("2000-02-02T08:00:00Z") } 
{ "sex" : "male", "bday" : ISODate("1987-02-02T08:00:00Z") } 
{ "sex" : "female", "bday" : ISODate("1989-02-02T08:00:00Z") } 
{ "sex" : "female", "bday" : ISODate("1993-11-02T08:00:00Z") } 
> db.client.aggregate([ project1, project2, group, project3 ]) 
{ 
    "result" : [ 
     { 
      "sex" : "female", 
      "total" : 2, 
      "averageAge" : 21.34109589041096 
     }, 
     { 
      "sex" : "male", 
      "total" : 2, 
      "averageAge" : 19.215068493150685 
     } 
    ], 
    "ok" : 1 
} 
> 

Lý do này là không đơn giản là hiện Aggregation Framework không hỗ trợ trừ trực tiếp của ngày tháng. Vui lòng bỏ phiếu cho https://jira.mongodb.org/browse/SERVER-6239 được nhắm mục tiêu cho bản phát hành chính tiếp theo - khi nó được triển khai, nó sẽ cho phép trừ trực tiếp các ngày (mặc dù bạn vẫn sẽ cần phải chuyển đổi ngày thành chi tiết thích hợp, nhiều năm trong trường hợp này).

+0

Tất nhiên, một cách khác để làm điều đó có thể là chuyển đổi bday thành ngày, nhận được số lượng người dùng trong nhóm và trong giai đoạn tính toán cuối cùng của dự án từ ngày hôm nay trong ngày trừ bday trong ngày chia cho 365. –

+0

Cảm ơn Asya, cách tiếp cận tương tự như trên. –

Các vấn đề liên quan