MongoDB агрегат в пределах ежедневной группировки
У меня есть некоторые документы в манго, которые выглядят примерно так:
{
_id : ObjectId("..."),
"make" : "Nissan",
..
},
{
_id : ObjectId("..."),
"make" : "Nissan",
"saleDate" : ISODate("2013-04-10T12:39:50.676Z"),
..
}
В идеале я хотел бы иметь возможность подсчитать количество продаваемых автомобилей в день. Затем мне хотелось бы посмотреть или сегодня, или окно, такое как сегодня, через последние семь дней.
Я смог выполнить ежедневный просмотр с помощью некоторого уродливого кода
db.inventory.aggregate(
{ $match : { "saleDate" : { $gte: ISODate("2013-04-10T00:00:00.000Z"), $lt: ISODate("2013-04-11T00:00:00.000Z") } } } ,
{ $group : { _id : { make : "$make", saleDayOfMonth : { $dayOfMonth : "$saleDate" } }, cnt : { $sum : 1 } } }
)
Что дает результаты
{
"result" : [
{
"_id" : {
"make" : "Nissan",
"saleDayOfMonth" : 10
},
"cnt" : 2
},
{
"_id" : {
"make" : "Toyota",
"saleDayOfMonth" : 10
},
"cnt" : 4
},
],
"ok" : 1
}
Итак, это нормально, но я бы предпочел не менять два значения datetime в запросе. Затем, как я уже упоминал выше, я хотел бы иметь возможность запускать этот запрос (опять же, без необходимости изменять его каждый раз) и видеть те же результаты, которые были сведены к концу дня за последнюю неделю.
О, и вот примеры данных, которые я использовал для запроса
db.inventory.save({"make" : "Nissan","saleDate" : ISODate("2013-04-10T12:39:50.676Z")});
db.inventory.save({"make" : "Nissan"});
db.inventory.save({"make" : "Nissan","saleDate" : ISODate("2013-04-10T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-09T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:38:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:37:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:36:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" : ISODate("2013-04-10T11:35:50.676Z")});
Спасибо заранее,
Кевин
Ответы
Ответ 1
В Mongo 2.8 RC2 появился новый оператор агрегации данных: $dateToString, который можно использовать для группировки по дням и просто иметь "ГГГГ-ММ-ДД" в результате:
Пример из документации:
db.sales.aggregate(
[
{
$project: {
yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
time: { $dateToString: { format: "%H:%M:%S:%L", date: "$date" } }
}
}
]
)
приведет к:
{ "_id" : 1, "yearMonthDay" : "2014-01-01", "time" : "08:15:39:736" }
Ответ 2
Возможно, вы захотите взглянуть на мою запись в блоге о том, как справиться с различными манипуляциями с датами в структуре агрегации здесь.
Что вы можете сделать, это использовать фазу $project
, чтобы урезать даты до ежедневного разрешения, а затем запустить агрегацию по всему набору данных (или только его часть) и агрегировать по дате и сделать.
С вашими примерными данными, скажем, вы хотите узнать, сколько автомобилей вы продали по маркам, по дате в этом году:
match={"$match" : {
"saleDate" : { "$gt" : new Date(2013,0,1) }
}
};
proj1={"$project" : {
"_id" : 0,
"saleDate" : 1,
"make" : 1,
"h" : {
"$hour" : "$saleDate"
},
"m" : {
"$minute" : "$saleDate"
},
"s" : {
"$second" : "$saleDate"
},
"ml" : {
"$millisecond" : "$saleDate"
}
}
};
proj2={"$project" : {
"_id" : 0,
"make" : 1,
"saleDate" : {
"$subtract" : [
"$saleDate",
{
"$add" : [
"$ml",
{
"$multiply" : [
"$s",
1000
]
},
{
"$multiply" : [
"$m",
60,
1000
]
},
{
"$multiply" : [
"$h",
60,
60,
1000
]
}
]
}
]
}
}
};
group={"$group" : {
"_id" : {
"m" : "$make",
"d" : "$saleDate"
},
"count" : {
"$sum" : 1
}
}
};
Теперь запуск агрегации дает вам:
db.inventory.aggregate(match, proj1, proj2, group)
{
"result" : [
{
"_id" : {
"m" : "Toyota",
"d" : ISODate("2013-04-10T00:00:00Z")
},
"count" : 4
},
{
"_id" : {
"m" : "Toyota",
"d" : ISODate("2013-04-09T00:00:00Z")
},
"count" : 1
},
{
"_id" : {
"m" : "Nissan",
"d" : ISODate("2013-04-10T00:00:00Z")
},
"count" : 2
}
],
"ok" : 1
}
Вы можете добавить еще одну фазу {$ project}, чтобы добавить результат, и вы можете добавить шаг {$ sort}, но в основном для каждой даты, для каждого из них вы получите счет того, сколько было продано.
Ответ 3
Мне нравится user1083621, но этот метод вызывает некоторые ограничения в следующих операциях с этим полем - потому что вы не можете использовать его как поле даты в (например) следующем агрегации. Вы не можете ни сравнивать, ни использовать операции агрегации дат, и после агрегации вы будете иметь строки (!). Все это можно решить, проецируя исходное поле даты, но в этом случае вы столкнетесь с некоторыми трудностями, сохранив его на этапе групповой работы. И в конце концов, иногда вы просто хотите манипулировать с началом дня, а не с произвольным дневным временем. Итак, вот мой метод:
{'$project': {
'start_of_day': {'$subtract': [
'$date',
{'$add': [
{'$multiply': [{'$hour': '$date'}, 3600000]},
{'$multiply': [{'$minute': '$date'}, 60000]},
{'$multiply': [{'$second': '$date'}, 1000]},
{'$millisecond': '$date'}
]}
]},
}}
Это дает вам следующее:
{
"start_of_day" : ISODate("2015-12-03T00:00:00.000Z")
},
{
"start_of_day" : ISODate("2015-12-04T00:00:00.000Z")
}
Невозможно сказать, если он быстрее, чем user1083621.