MongoDB Условная сумма элементов массива для коллекции документов - PullRequest
1 голос
/ 26 февраля 2020

У меня есть следующая коллекция документов MongoDB, каждый из которых содержит поле с именем «history», которое содержит массив поддокументов с полями «date» и «points».

[{
    history: [{
        date: "2019-20-20",
        points: 1,
    }, {
        date: "2019-20-21",
        points: 1,
    }, {
        date: "2019-20-22",
        points: 1,
    }, {
        date: "2019-20-23",
        points: 1,
    }],
}, {
    history: [{
        date: "2019-20-20",
        points: 1,
    }, {
        date: "2019-20-21",
        points: 2,
    }, {
        date: "2019-20-22",
        points: 3,
    }, {
        date: "2019-20-23",
        points: 4,
    }],
}]

Я не уверен, каков наилучший способ построить запрос, который выдает следующие результаты. Для следующего примера диапазон дат (включительно) - от «2019-20-21» до «2019-20-22». «totalPoints» - это новое поле, которое содержит сумму всех точек в поле «история» в этом диапазоне дат.

[{
    history: [{
        date: "2019-20-20",
        points: 1,
    }, {
        date: "2019-20-21",
        points: 1,
    }, {
        date: "2019-20-22",
        points: 1,
    }, {
        date: "2019-20-23",
        points: 1,
    }],
    totalPoints: 2,
}, {
    history: [{
        date: "2019-20-20",
        points: 1,
    }, {
        date: "2019-20-21",
        points: 2,
    }, {
        date: "2019-20-22",
        points: 3,
    }, {
        date: "2019-20-23",
        points: 4,
    }],
    totalPoints: 5,
}]

Ниже приведено общее представление о том, что я пытаюсь сделать:

User.aggregate([{
    $addFields: {
        totalPoints: { $sum: points in "history" field if date range between "2019-20-21" and "2019-20-22" } ,
    }
}]);

Причина, по которой я хочу создать новое поле "totalPoints", заключается в том, что в конечном итоге я хочу отсортировать через поле "totalPoints".

1 Ответ

1 голос
/ 26 февраля 2020

Для одного конвейера вы можете объединить $reduce с $filter, чтобы получить сумму следующим образом:

var startDate = "2019-20-21";
var endDate = "2019-20-22";
User.aggregate([
    { "$addFields": { 
        "totalPoints": {
            "$reduce": {
                "input": {
                    "$filter": {
                        "input": "$history",
                        "as": "el",
                        "cond": {
                            "$and": [
                                { "$gte": ["$$el.date", startDate] },
                                { "$lte": ["$$el.date", endDate ] },
                            ]
                        }
                    }
                },
                "initialValue": 0,
                "in": { "$add": [ "$$value", "$$this.points" ] }
            }
        }
    } }
]);

Другой альтернативой является наличие двух этапов конвейера, где вы начинаете агрегацию с отфильтрованным массивом, который содержит только элементы, соответствующие запросу диапазона дат. Объедините $addFields с $filter для этого, и ваше условие фильтрации использует условный оператор $and с операторами сравнения $gte и $lte. Следующий конвейер показывает это:

{ "$addFields": { 
    "totalPoints": {
        "$filter": {
            "input": "$history",
            "cond": {
                "$and": [
                    { "$gte": ["$$this.date", "2019-20-21"] },
                    { "$lte": ["$$this.date", "2019-20-22"] },
                ]
            }
        }
    }
} },

После получения отфильтрованного массива вы можете легко получить сумму в следующем конвейере с помощью $sum, так что ваш полный конвейер станет

var startDate = "2019-20-21";
var endDate = "2019-20-22";
User.aggregate([
    { "$addFields": { 
        "totalPoints": {
            "$filter": {
                "input": "$history",
                "cond": {
                    "$and": [
                        { "$gte": ["$$this.date", startDate] },
                        { "$lte": ["$$this.date", endDate ] },
                    ]
                }
            }
        }
    } },
    { "$addFields": { 
        "totalPoints": { "$sum": "$totalPoints.points" }
    } }
])

...