#mongodb #mongodb-query #aggregation-framework #aggregation
#mongodb #mongodb-запрос #агрегация-фреймворк #агрегация
Вопрос:
Учитывая следующие данные:
> db.users.find({}, {name: 1, createdAt: 1, updatedAt: 1}).limit(5).pretty()
{
"_id" : ObjectId("5ec8f74f32973c7b7cb7cce9"),
"createdAt" : ISODate("2020-05-23T10:13:35.012Z"),
"updatedAt" : ISODate("2020-08-20T13:37:09.861Z"),
"name" : "Patrick Jere"
}
{
"_id" : ObjectId("5ec8ef8a2b6e5f78fa20443c"),
"createdAt" : ISODate("2020-05-23T09:40:26.089Z"),
"updatedAt" : ISODate("2020-07-23T07:54:01.833Z"),
"name" : "Austine Wiga"
}
{
"_id" : ObjectId("5ed5e1a3962a3960ad85a1a2"),
"createdAt" : ISODate("2020-06-02T05:20:35.090Z"),
"updatedAt" : ISODate("2020-07-29T14:02:52.295Z"),
"name" : "Biasi Phiri"
}
{
"_id" : ObjectId("5ed629ec6d87382c608645d9"),
"createdAt" : ISODate("2020-06-02T10:29:00.204Z"),
"updatedAt" : ISODate("2020-06-02T10:29:00.204Z"),
"name" : "Chisambwe Kalusa"
}
{
"_id" : ObjectId("5ed8d21f42bc8115f67465a8"),
"createdAt" : ISODate("2020-06-04T10:51:11.546Z"),
"updatedAt" : ISODate("2020-06-04T10:51:11.546Z"),
"name" : "Wakun Moyo"
}
...
Я использую следующий запрос для возврата new_users
по месяцам:
db.users.aggregate([
{
$group: {
_id: {$dateToString: {format: '%Y-%m', date: '$createdAt'}},
new_users: {
$sum: {$ifNull: [1, 0]}
}
}
}
])
пример результата:
[
{
"_id": "2020-06",
"new_users": 125
},
{
"_id": "2020-07",
"new_users": 147
},
{
"_id": "2020-08",
"new_users": 43
},
{
"_id": "2020-05",
"new_users": 4
}
]
и этот запрос возвращает new_users
, active_users
и total users
за определенный месяц.
db.users.aggregate([
{
$group: {
_id: null,
new_users: {
$sum: {
$cond: [{
$gte: ['$createdAt', ISODate('2020-08-01')]
}, 1, 0]
}
},
active_users: {
$sum: {
$cond: [{
$gt: ['$updatedAt', ISODate('2020-02-01')]
}, 1, 0]
}
},
total_users: {
$sum: {$ifNull: [1, 0]}
}
}
}
])
Как я могу заставить второй запрос возвращать результаты по месяцам, как и в первом запросе?
ожидаемые результаты на основе фильтра за один месяц:
[
{ _id: '2020-09', new_users: 0, active_users: 69},
{ _id: '2020-08', new_users: 43, active_users: 219},
{ _id: '2020-07', new_users: 147, active_users: 276},
{ _id: '2020-06', new_users: 125, active_users: 129},
{ _id: '2020-05', new_users: 4, active_users: 4}
]
Ответ №1:
Вы можете попробовать агрегацию ниже.
Подсчитайте новых пользователей, а затем просмотрите, чтобы подсчитать активных пользователей для временного окна за каждый месяц года.
db.users.aggregate([
{"$group":{
"_id":{"$dateFromParts":{"year":{"$year":"$createdAt"},"month":{"$month":"$createdAt"}}},
"new_users":{"$sum":1}
}},
{"$lookup":{
"from":"users",
"let":{"end_date":"$_id", "start_date":{"$dateFromParts":{"year":{"$year":"$_id"},"month":{"$subtract":[{"$month":"$_id"},1]}}}},
"pipeline":[
{"$match":{"$expr":
{"$and":[{"$gte":[
"$updatedAt",
"$$start_date"
]}, {"$lt":[
"$updatedAt",
"$$end_date"
]}]}
}},
{"$count":"activeUserCount"}
],
"as":"activeUsers"
}},
{"$project":{
"year-month":{"$dateToString":{"format":"%Y-%m","date":"$_id"}},
"new_users":1,
"active_users":{"$arrayElemAt":["$activeUsers.activeUserCount", 0]},
"_id":0
}}])
Комментарии:
1. что произойдет, когда месяц в _id равен 1 и при вычитании с помощью
"$subtract": [ {"$month":"$_id"},1]
, справится ли он с$dateFromParts
или потерпит неудачу?2. Как насчет чего-то подобного
{$subtract: [ _id, 2592000000 ]}
? Это число равно месяцу в миллисекундах.3. @francis — нет необходимости в месяцах в миллисекундах — дата из частей может обрабатывать переполнение, и она соответствующим образом скорректирует год. Вы можете проверить свои данные.
4. @turivishal — это не приведет к сбою и в данном случае будет соответствовать предыдущему году и месяцу, такому как декабрь.
Ответ №2:
Вы можете сделать то же самое, что и в первом запросе, group by cteatedAt
, нет необходимости использовать $ifNull
оператор в total_users
,
Обновлено,
- используйте
$facet
группировку по месяцам и подсчет для обоих показателей $project
объединить оба массива с помощью$concatArrays
$unwind
деконструировать массивroot
$group
по месяцам и объединить как месяц, так и количество
Комментарии:
1. Я обновил вопрос, чтобы включить пример результата. Так
active_users
должны быть пользователи, у которыхupdatedAt
есть$gt
дата в_id
минус 6 месяцев. Например, если_id
есть2020-07
, то$cond
должно быть{ $gt: ["$updatedAt", ISODate("2020-01-01")] }
.2.
ISODate
должно быть динамически сгенерировано на основе_id
.3. хорошо, вы можете добавить ожидаемый результат в соответствии с вашими документами поиска.
4. Я добавил ссылку на образец данных и ожидаемые результаты на основе фильтра за 1 месяц, поскольку данные возвращаются только за 5 месяцев.
5. Я не до конца понимаю, как
$project
работает, но не могу избавиться от ощущения, что это может помочь. На первом этапе мы можем захватить все даты, а на втором этапе применить условие на основе этих дат.