2012-10-20 3 views
5

Ich habe eine einfache db Layout wie folgt aus:Berechnung Zahl und durchschnittliche mit MongoDB Aggregation

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

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

(...) 

Ich versuche, eine Aggregation Befehl zu schreiben, gibt, wie viele männliche und weibliche Kunden die ich habe, und ich Würde ich auch gerne das Durchschnittsalter von Männchen und Weibchen ausgeben, nicht sicher, ob ich das im selben Kommando machen kann oder ich brauche 2 getrennte?

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

Mit dem obigen Code ich die Grafen von männlich/weiblich, aber avgAge kommt als 0 Irgendwelche Ideen?

Vielen Dank

+0

Beachten Sie, dass Sie die Felder sexCount oder avgAge im ersten Schritt nicht projizieren müssen, da dies die Felder sind, die Sie im Schritt $ group berechnen. –

Antwort

4

Das Objekt Datum kann nicht sein „gemittelt“, aber die Zahlen können. Sie können Ihre Daten in den Zeitmarkenwert umwandeln und dann den Durchschnitt daraus ermitteln. Aber das ist immer noch kein Durchschnittsalter, Sie müssen das Ergebnis vom aktuellen Datum außerhalb der Aggregationsfunktion subtrahieren. Eine andere Möglichkeit ist anzunehmen, dass das Alter nur mit einem Jahr des Datums berechnet werden kann (das heißt, wenn ich am 1. Dezember 2000 geboren wurde, bin ich im heutigen Bericht 12 Jahre alt und nicht 11 Jahre alt). In diesem Fall können Sie date operators verwenden, um den Jahreswert zu extrahieren. obwohl

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

Danke. Habe gerade gemerkt, dass Birthday als String gespeichert ist ("Sa 22. Mai 1982 00:00:00 GMT + 0200"), was die Dinge etwas schwieriger macht. Ist es möglich, es als eine Zahl zu werfen? Ich habe versucht, einen Teilstrg zu machen, um nur den Jahrteil zu bekommen, aber dann habe ich Schwierigkeiten, das in eine Zahl umzuwandeln, um dann den $ subtrahieren zu machen, den du vorschlägst. Wenn das schwierig ist, dann denke ich, ich kann das Feld in ein Datum verwandeln. –

+0

Die Konvertierung ist nicht Teil des Aggregations-Framework, ich denke, Sie müssen entweder MapReduce verwenden, wo Sie beliebigen JavaScript-Code schreiben können, oder Ihre Datenbank durchlaufen und alle Daten konvertieren. – Dmitry

+0

Danke! Ich habe das Jahr in ein separates Feld extrahiert und kann nun ganz einfach den Durchschnitt machen. –

6

Die Antwort wäre viel einfacher, wenn Sie Alter in dem Originaldokument zu speichern wurden (wie Dmitry geschrieben, könnte man einfach eine gerade avgAge:{$avg:"$age"} in Ihrem $group Schritt tun.

Aggregation-Framework ziemlich raffinierte und hat viele coole Operatoren, die Sie zu berechnen, diese fehlenden Altersfeld „on the fly“

ich werde speichern jeden Schritt der Aggregation in einer variablen ermöglichen, so dass es leichter zu sehen ist, was vor sich geht.

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

Jetzt können Sie die Aggregation ausführen:

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

Der Grund, warum dies nicht einfach ist, ist derzeit Aggregation Framework nicht direkte Subtraktion der Daten unterstützt. Bitte stimmen Sie für https://jira.mongodb.org/browse/SERVER-6239, die für die nächste Hauptversion ausgerichtet ist - sobald es implementiert ist, sollte es Subtraktion von Daten direkt zulassen (obwohl Sie es immer noch in die passende Granularität konvertieren müssen, Jahre in diesem Fall wahrscheinlich).

+0

natürlich eine andere Möglichkeit, es zu tun, um Bday in Tage zu konvertieren, erhalten Sie die Avg von denen in der Gruppe Schritt und im endgültigen Projekt berechnen Alter von heute in Tagen minus Bday in Tagen durch 365 geteilt. –

+0

Dank Asya, ähnlicher Ansatz wie oben. –