node.js and Вычисление счета и среднее значение с агрегацией MongoDB



mongoose find and aggregate (2)

У меня простой формат db:

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

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

(...)

Я пытаюсь написать команду агрегации, которая выводит, сколько у меня мужчин и женщин, и я также хотел бы вывести средний возраст мужчин и женщин, не уверенный, что могу сделать это в той же команде, или я нужны 2 отдельных?

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

С кодом выше я получаю подсчеты мужчин / женщин, но avgAge приходит как 0. Любые идеи?

Большое спасибо


Answer #1

Объект даты не может быть «усредненным», но номера могут. Вы можете конвертировать свои даты в значение отметки времени, а затем найти среднее значение из него. Но все же это не будет средний возраст, вам нужно будет вычесть результат с текущей даты вне функции агрегации.

Другой вариант - предположить, что возраст можно рассчитать, используя только часть года (то есть, если я родился 1 декабря 2000 года, в сегодняшнем отчете мне будет 12 лет, а не 11). В этом случае вы можете использовать операторов дат для извлечения значения года.

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

Answer #2

Ответ был бы намного проще, если бы вы сохранили возраст в оригинальном документе (как avgAge:{$avg:"$age"} Дмитрий, вы можете просто сделать прямой avgAge:{$avg:"$age"} на вашем шаге $group .

Агрегационная структура довольно изящна, хотя и имеет много классных операторов, которые позволяют вам вычислять это поле с отсутствующим возрастом «на лету».

Я собираюсь хранить каждый шаг агрегации в переменной, чтобы было легче увидеть, что происходит:

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

Теперь вы можете запустить агрегацию:

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

Причина в том, что это не так просто, в настоящее время Aggregation Framework не поддерживает прямое вычитание дат. Пожалуйста, проголосуйте за https://jira.mongodb.org/browse/SERVER-6239, который предназначен для следующего крупного выпуска - после его внедрения он должен позволить прямое вычитание дат (хотя вам все равно нужно будет преобразовать его в соответствующую степень детализации, лет в этом случае, вероятно).





aggregation-framework