Выполнение case-statement в структуре агрегации mongodb
Я оцениваю, насколько хорошо структура агрегации MongoDB соответствует нашим потребностям, поскольку мы в настоящее время работаем над SQL Server. Мне сложно выполнить конкретный запрос:
Скажем, у меня есть следующие псевдозаписи (смоделированные как столбцы в таблице sql и как полный документ в коллекции mongodb)
{
name: 'A',
timespent: 100,
},
{
name: 'B',
timespent: 200,
},
{
name: 'C',
timespent: 300,
},
{
name: 'D',
timespent: 400,
},
{
name: 'E',
timespent: 500,
}
Я хочу сгруппировать поле timepent в диапазоны и подсчитать случаи, чтобы я получил, например. следующие псевдо-записи:
results{
0-250: 2,
250-450: 2,
450-650: 1
}
Обратите внимание, что эти диапазоны (250, 450 и 650) являются динамическими и, вероятно, будут изменяться с течением времени пользователем. В SQL мы извлекли результаты примерно так:
select range, COUNT(*) as total from (
select case when Timespent <= 250 then '0-250'
when Timespent <= 450 then '200-450'
else '450-600' end as range
from TestTable) as r
group by r.range
Снова отметим, что этот sql динамически создается нашим приложением, чтобы соответствовать определенным диапазонам, доступным в любой момент времени.
Я пытаюсь найти соответствующие конструкции в структуре агрегации mongodb для выполнения таких запросов. Я могу запросить результаты одного диапазона, вставив $match в конвейер (т.е. Получив результат одного диапазона), но я не могу понять, как извлечь все диапазоны и их подсчеты в одном запросе конвейера.
Ответы
Ответ 1
что соответствует оператору SQL "case" в структуре агрегации, является оператором $cond (см. manual). Операторы $cond могут вставляться в симуляцию "when-then" и "else", но я выбрал другой подход, потому что его легче читать (и генерировать, см. ниже): я буду использовать оператор $concat для записи строка диапазона, которая затем служит в качестве ключа группировки.
Итак, для данного набора:
db.xx.find()
{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 }
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 }
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 }
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 }
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 }
совокупный (жесткий код) выглядит следующим образом:
db.xx.aggregate([
{ $project: {
"_id": 0,
"range": {
$concat: [{
$cond: [ { $lte: ["$timespent", 250] }, "range 0-250", "" ]
}, {
$cond: [ { $and: [
{ $gte: ["$timespent", 251] },
{ $lt: ["$timespent", 450] }
] }, "range 251-450", "" ]
}, {
$cond: [ { $and: [
{ $gte: ["$timespent", 451] },
{ $lt: ["$timespent", 650] }
] }, "range 450-650", "" ]
}]
}
}},
{ $group: { _id: "$range", count: { $sum: 1 } } },
{ $sort: { "_id": 1 } },
]);
и результат:
{
"result" : [
{
"_id" : "range 0-250",
"count" : 2
},
{
"_id" : "range 251-450",
"count" : 2
},
{
"_id" : "range 450-650",
"count" : 1
}
],
"ok" : 1
}
Чтобы сгенерировать команду aggregate, вам нужно построить проекцию "диапазона" в качестве объекта JSON (или вы можете сгенерировать строку, а затем использовать JSON.parse(string))
Генератор выглядит так:
var ranges = [ 0, 250, 450, 650 ];
var rangeProj = {
"$concat": []
};
for (i = 1; i < ranges.length; i++) {
rangeProj.$concat.push({
$cond: {
if: {
$and: [{
$gte: [ "$timespent", ranges[i-1] ]
}, {
$lt: [ "$timespent", ranges[i] ]
}]
},
then: "range " + ranges[i-1] + "-" + ranges[i],
else: ""
}
})
}
db.xx.aggregate([{
$project: { "_id": 0, "range": rangeProj }
}, {
$group: { _id: "$range", count: { $sum: 1 } }
}, {
$sort: { "_id": 1 }
}]);
который вернет тот же результат, что и выше.
Ответ 2
Начиная с MongoDB 3.4, мы можем использовать оператор $switch
для выполнения оператора multi-switch на этапе $project
.
$group
оператор-оператор группирует документы по "диапазону" и возвращает "счет" для каждой группы с помощью $sum
.
db.collection.aggregate(
[
{ "$project": {
"range": {
"$switch": {
"branches": [
{
"case": { "$lte": [ "$timespent", 250 ] },
"then": "0-250"
},
{
"case": {
"$and": [
{ "$gt": [ "$timespent", 250 ] },
{ "$lte": [ "$timespent", 450 ] }
]
},
"then": "251-450"
},
{
"case": {
"$and": [
{ "$gt": [ "$timespent", 450 ] },
{ "$lte": [ "$timespent", 650 ] }
]
},
"then": "451-650"
}
],
"default": "650+"
}
}
}},
{ "$group": {
"_id": "$range",
"count": { "$sum": 1 }
}}
]
)
Со следующими документами в нашей коллекции
{ "_id" : ObjectId("514919fb23700b41723f94dc"), "name" : "A", "timespent" : 100 },
{ "_id" : ObjectId("514919fb23700b41723f94dd"), "name" : "B", "timespent" : 200 },
{ "_id" : ObjectId("514919fb23700b41723f94de"), "name" : "C", "timespent" : 300 },
{ "_id" : ObjectId("514919fb23700b41723f94df"), "name" : "D", "timespent" : 400 },
{ "_id" : ObjectId("514919fb23700b41723f94e0"), "name" : "E", "timespent" : 500 }
наш запрос дает:
{ "_id" : "451-650", "count" : 1 }
{ "_id" : "251-450", "count" : 2 }
{ "_id" : "0-250", "count" : 2 }
Мы можем захотеть добавить $sort
к конвейеру, сортируя наш документ по диапазону, но это будет сортировать документы только в лексикографический порядок из-за типа "диапазон".