Сравните даты из массивов различных объектов в агрегации - PullRequest
0 голосов
/ 06 сентября 2018

в моем проекте у меня есть пользователи, которые заканчивают комбинации (называемые сессиями) курсов. сам факт прохождения курса называется попыткой. Во время попытки они могут закрыть его и вернуться позже (поэтому мы храним объект timelog).

У меня есть запрос от клиента, который должен возвращать для каждого сеанса пользователей (и их попытки), которые сыграли весь или часть своего сеанса в течение определенного периода времени.

В течение определенного периода времени означает, что клиент отправляет дату начала и окончания, и мы считаем пользователя для определенного сеанса, если: - первая попытка началась до истечения таймфрейма => начало первого таймлога первой <дата окончания - последняя попытка была завершена после начала таймфрейма => конец последнего таймлога последней попытки> дата начала

Вот пример объекта попытки (единственный, который нам нужно использовать здесь):

{
"_id" : ObjectId("5b9148650ab5f43b5e829a4b"),
"index" : 0,
"author" : ObjectId("5acde2646055980a84914b6b"),
"timelog" : [
    {
        "started" : ISODate("2018-09-06T15:31:49.163Z"),
        "ended" : ISODate("2018-09-06T15:32:03.935Z")
    },
    ...
],
"session" : ObjectId("5b911d31e58dc13ab7586f9b")}
  • Моя идея состояла в том, чтобы объединить попытки, сгруппировать тех, кто использует author и session в качестве _id для этапа $group, и отодвинуть все попытки пользователя для этого конкретного сеанса. в массив userAttempts.
  • Затем создать этап $ addField для извлечения начального поля первого временного журнала первой попытки и последнего конца последней попытки.
  • И, наконец, $ filter или $ match с использованием этих новых полей.

Вот мой агрегат:

const newDate = new Date()
_db.attempts.aggregate([
        { $match: {
            author: { $in: programSessionsData.users },
            $or: [{ programSession: { $in: programSessionIds } }, { oldTryFor: { $in: programSessionIds } }],
            globalTime: $ex,
            timelog: $ex }
        },
        {
            $group: {
                _id: {
                    user: "$author",
                    programSession: "$programSession"
                },
                userAttempts: { $push: { attemptId: "$_id", lastTimelog: { $arrayElemAt: ["$timelog", -1] }, timelog: "$timelog" } }
            }
        },
        {
            $addFields: { begin: { $reduce: {
                input: "$userAttempts",
                initialValue: newDate,
                in: {
                    $cond: {
                        if: { $lt: ["$$this.timelog.0.started", "$$value"] },
                        then: "$$this.timelog.0.started",
                        else: "$$value"
                    } }
            } } }
        }

Я также пробовал это на этапе addFields:

{
            $addFields: { begin: { $reduce: {
                input: "$userAttempts",
                initialValue: newDate,
                in: { $min: ["$$this.timelog.0.started", "$$value] }
            } } }
}

Однако каждый раз начало - это пустой массив. Я действительно не знаю, как я могу извлечь эти две даты или сравнить даты между ними.

К сведению : конец сложнее, поэтому мне нужно сначала извлечь lastTimelog. Если бы вы использовали другой метод, я бы с радостью воспользовался им.

Также этот код находится на сервере узлов, поэтому я не могу использовать ISODate. и используемая версия монго - 3.6.3.

1 Ответ

0 голосов
/ 10 сентября 2018

Немного поиграв с агрегатом, я предложил 2 решения:

Раствор 1

_db.attempts.aggregate([
        { $match: {
            query
        },
        {
            $group: {
                _id: {
                    user: "$author",
                    programSession: "$programSession"
                },
                userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog" } }
            }
        }, {
            $addFields: {
                begin: { $reduce: {
                    input: "$userAttempts",
                    initialValue: newDate,
                    in: { $min: [{ $reduce: {
                        input: "$$this.timelog",
                        initialValue: newDate,
                        in: { $min: ["$$this.started", "$$value"] }
                    } }, "$$value"] }
                } },
                end: { $reduce: {
                    input: "$userAttempts",
                    initialValue: oldDate,
                    in: { $max: [{ $reduce: {
                        input: "$$this.timelog",
                        initialValue: oldDate,
                        in: { $max: ["$$this.ended", "$$value"] }
                    } }, "$$value"] }
                } }
            }
        },
        {
            $match: {
                begin: { $lt: req.body.ended },
                end: { $gt: req.body.started }
            }
        }
    ], { allowDiskUse: true });

newDate - это сегодня, а oldDate - произвольная дата в прошлом. Мне пришлось сократить цепочку 2, потому что «$$ this.timelog.0.started» всегда ничего не возвращает. Хотя на самом деле не знаю почему.

Раствор 2

_db.attempts.aggregate([
        { $match: {
           query
        },
        {
            $addFields: {
                firstTimelog: { $arrayElemAt: ["$timelog", 0] },
                lastTimelog: { $arrayElemAt: ["$timelog", -1] }
            }
        },
        {
            $group: {
                _id: {
                    user: "$author",
                    programSession: "$programSession"
                },
                begin: { $min: "$firstTimelog.started" },
                end: { $max: "$lastTimelog.ended" },
                userAttempts: { $push: { attemptId: "$_id", timelog: "$timelog"} }
            }
        },
        {
            $match: {
                begin: { $lt: req.body.ended },
                end: { $gt: req.body.started }
            }
        }
    ], { allowDiskUse: true });

Это намного проще и кажется проще, но, как ни странно, из моего тестирования Solution 1 всегда быстрее, по крайней мере, в распределении объектов для моего проекта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...