Суммарный агрегат Mongodb для нескольких диапазонов дат
В моей совокупности каждый документ в потоке будет иметь дату на нем.
Мне нужно суммировать некоторые значения в диапазонах дат.
Т.е.
{
value: 3,
date: [SoME TIME STAMP]
},
{
value: 4,
date: [SoME TIME STAMP]
},
{
value: 1,
date: [SoME TIME STAMP]
},
{
value: -6,
date: [SoME TIME STAMP]
}
Я хочу иметь возможность группировать эту базу документов в диапазоне дат. IE: 1-7 дней назад, 8-15 дней назад. и 15-30 дней назад.
db.Collection.aggregate([
{$match: {some matching}},
{$group: {What should i do here??}}
])
Я могу, конечно, сделать 3 разных агрегата с 3 разными $match в датах.
Возможно ли выполнить всю группу $и суммировать поле "значение" за один проход?
Ответы
Ответ 1
Вам необходимо условно определить ключ группировки на основе того, где текущая дата находится между диапазонами. В основном это достигается с помощью $cond
с вложенными условиями и логическим вариантом $lt
:
// work out dates somehow
var today = new Date(),
oneDay = ( 1000 * 60 * 60 * 24 ),
thirtyDays = new Date( today.valueOf() - ( 30 * oneDay ) ),
fifteenDays = new Date( today.valueOf() - ( 15 * oneDay ) ),
sevenDays = new Date( today.valueOf() - ( 7 * oneDay ) );
db.collection.aggregate([
{ "$match": {
"date": { "$gte": thirtyDays }
}},
{ "$group": {
"_id": {
"$cond": [
{ "$lt": [ "$date", fifteenDays ] },
"16-30",
{ "$cond": [
{ "$lt": [ "$date", sevenDays ] },
"08-15",
"01-07"
]}
]
},
"count": { "$sum": 1 },
"totalValue": { "$sum": "$value" }
}}
])
Так как $cond
является троичным оператором, первое условие оценивается, чтобы увидеть, является ли условие истинным, и когда оно истинно, возвращается второй аргумент, в противном случае третий возвращается, когда ложно. Таким образом, вложив другой $cond
в ложный случай, вы получите логический тест на то, куда попадает дата: либо "меньше 15-дневной даты", что означает, что она находится в самом старом диапазоне, либо "менее 7 дней", что означает средний диапазон, или, конечно, в новейшем диапазоне.
Я просто добавляю здесь цифры, меньшие 10, к 0
, так что он дает вам возможность сортировать, если хотите, поскольку вывод "ключей" в $group
не упорядочен сам по себе.
Но это то, как вы делаете это в одном запросе. Вы просто решаете, какой ключ группировки должен быть основан на том, куда попадает дата, и накапливаете для каждого ключа.
Ответ 2
Первым шагом будет создание объектов даты, которые представляют ваш диапазон. Допустим, вы хотите запустить операцию агрегирования для перехода 8-15 дней назад, это означает, что вам нужны два объекта даты, скажем, начало и конец. start будет содержать дату днем ранее, а end - 8 дней назад. Создать эти объекты даты легко, если установить для них число дней, предшествовавших вычитанию n
из даты, где n
- это число дней назад:
var start = new Date();
start.setDate(start.getDate() - 8);
var end = new Date();
end.setDate(end.getDate() - 15);
или вычитание из отметки времени в миллисекундах с использованием метода .getTime()
возвращает стандартную отметку времени JavaScript (в миллисекундах с момента Jan 1/1970
), в которой вы можете использовать обычные математические операции, и напрямую передается объекту Date:
var today = new Date();
var start = new Date(today.getTime() - 8*24*60*60*1000);
var end = new Date(today.getTime() - 15*24*60*60*1000);
Теперь, когда у вас есть объекты даты, вы можете использовать их в качестве $match
критериев, используя $lte
и $gte
операторы сравнения:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
}
]
Выполнение агрегации на этом этапе даст вам все документы, дата которых находится в диапазоне 8-15 дней назад,
db.aggregate(pipeline);
что эквивалентно запросу find()
:
db.collection.find({
"date": { "$gte": start, "$lte": end }
});
Теперь, на следующем этапе конвейера, вам нужно будет создать операцию агрегирования, которая задает группу _id
со значением NULL, вычисляя общее значение и счетчики для всех документов в коллекции, используя $sum
оператор аккумулятора:
var pipeline = [
{
"$match": {
"date": { "$gte": start, "$lte": end }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
]
db.collection.aggregate(pipeline);
Вы можете даже пойти дальше, чтобы создать универсальную функцию, которая возвращает фактическую сумму из вышеуказанной операции агрегирования, которая принимает два параметра: начальное значение диапазона дат и конец:
var getTotalValues = function(start, end){
var today = new Date();
var startDate = new Date(today.getTime() - start*24*60*60*1000);
var endDate = new Date(today.getTime() - end*24*60*60*1000);
var pipeline = [
{
"$match": {
"timestamp": { "$gte": startDate, "$lte": endDate }
}
},
{
"$group": {
"_id": null,
"totalValues": { "$sum": "$value" },
"count": { "$sum": 1 }
}
}
],
resultArray = db.collection.aggregate(pipeline).toArray();
return resultArray[0].totalValues;
}
var total = getTotalValues(1, 8);
printjson(total); // prints the total