Короче говоря, вы, кажется, смирились с настройкой своих моделей в mongoose и перешли за рамки ссылок.На самом деле вы не должны хранить массивы в документах "User"
.На самом деле это «анти-паттерн», который изначально был просто мангустом, использовавшимся в качестве соглашения для хранения «ссылок» для популяции, где он не понимал, как вместо этого переводить ссылки, хранящиеся в «потомке», в «родителя»..
Фактически у вас есть эти данные в каждом "Ticket"
, и естественная форма $lookup
состоит в том, чтобы использовать это "foreignField"
в отношении подробностей из локальной коллекции.В этом случае "assignee_id"
на билетах будет достаточно для просмотра соответствия обратно к "_id"
из "User"
.Хотя вы этого и не заявите, ваш "status"
должен быть индикатором того, действительно ли данные либо «назначены», как в состоянии «Ожидание», либо «разрешены», когда это не так.
Дляради простоты мы будем считать состояние «разрешенным», если оно имеет значение, отличное от «ожидающего решения», но расширение логики из примера для реальных потребностей здесь не является проблемой.
По существу, тогдамы разрешаем одну $lookup
операцию, фактически используя естественный «внешний ключ», в отличие от хранения отдельных массивов.
MongoDB 3.6 и выше
В идеале вы должны использовать функции MongoDB 3.6 с обработкой суб-конвейера здесь:
// Better date calculations
const oneDay = (1000 * 60 * 60 * 24);
var now = Date.now(),
end = new Date((now - (now % oneDay)) + oneDay),
start = new Date(end.valueOf() - (4 * oneDay));
User.aggregate([
{ "$match": { "organization": req.user.organization._id } },
{ "$lookup": {
"from": Ticket.collection.name,
"let": { "id": "$_id" },
"pipeline": [
{ "$match": {
"createdAt": { "$gte": start, "$lt": end },
"$expr": {
"$eq": [ "$$id", "$assignee_id" ]
}
}},
{ "$group": {
"_id": {
"status": "$status",
"date": {
"$dateFromParts": {
"year": { "$year": "$createdAt" },
"month": { "$month": "$createdAt" },
"day": { "$dayOfMonth": "$createdAt" }
}
}
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.date",
"data": {
"$push": {
"k": {
"$cond": [
{ "$eq": ["$_id.status", "Pending"] },
"assignedCount",
"resolvedCount"
]
},
"v": "$count"
}
}
}},
{ "$sort": { "_id": -1 } },
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
{ "groupDate": "$_id", "assignedCount": 0, "resolvedCount": 0 },
{ "$arrayToObject": "$data" }
]
}
}}
],
"as": "data"
}},
{ "$project": { "data": 1 } }
])
От MongoDB 3.0 и выше
Или, если вам не хватает этих функций, мы используем другой процесс конвейераи небольшое преобразование данных после того, как результаты будут возвращены с сервера:
User.aggregate([
{ "$match": { "organization": req.user.organization._id } },
{ "$lookup": {
"from": Ticket.collection.name,
"localField": "_id",
"foreignField": "assignee_id",
"as": "data"
}},
{ "$unwind": "$data" },
{ "$match": {
"data.createdAt": { "$gte": start, "$lt": end }
}},
{ "$group": {
"_id": {
"userId": "$_id",
"date": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$data.createdAt", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$data.createdAt", new Date(0) ] },
oneDay
]}
]},
new Date(0)
]
},
"status": "$data.status"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": {
"userId": "$_id.userId",
"date": "$_id.date"
},
"data": {
"$push": {
"k": {
"$cond": [
{ "$eq": [ "$_id.status", "Pending" ] },
"assignedCount",
"resolvedCount"
]
},
"v": "$count"
}
}
}},
{ "$sort": { "_id.userId": 1, "_id.date": -1 } },
{ "$group": {
"_id": "$_id.userId",
"data": {
"$push": {
"groupDate": "$_id.date",
"data": "$data"
}
}
}}
])
.then( results =>
results.map( ({ data, ...d }) =>
({
...d,
data: data.map(di =>
({
groupDate: di.groupDate,
assignedCount: 0,
resolvedCount: 0,
...di.data.reduce((acc,curr) => ({ ...acc, [curr.k]: curr.v }),{})
})
)
})
)
)
, что просто показывает, что даже с модными функциями в современныхаренда, вы действительно не нуждаетесь в них, потому что всегда были способы обойти это.Даже у частей JavaScript были только несколько более длинные версии, прежде чем был доступен текущий синтаксис «распространения объекта».
Так что это действительно то направление, в котором вам нужно идти. То, что вам определенно не нужно, это использовать «множественный»«$lookup
этапов или даже применение $filter
условий для потенциально больших массивов.Также обе формы здесь делают все возможное, чтобы «отфильтровать» количество элементов, «соединенных» из чужой коллекции, чтобы не нарушать ограничение BSON.
В частности, версия «pre 3.6» на самом деле имееттрюк, в котором $lookup + $unwind + $match
происходит подряд, что вы можете увидеть в выводе объяснения.Все этапы фактически объединяются в «один» этап, который возвращает только те элементы, которые соответствуют условиям в $match
из иностранной коллекции.Сохранение «раскручивания» до дальнейшего сокращения позволяет избежать проблем с ограничениями BSON, как это делает новая форма с MongoDB 3.6, где «под-конвейер» выполняет сокращение и группировку документов до того, как будут возвращены какие-либо результаты.
Ваш один образец документа будет возвращаться так:
{
"_id" : ObjectId("5aeb6b71709f43359e0888bb"),
"data" : [
{
"groupDate" : ISODate("2018-05-02T00:00:00Z"),
"assignedCount" : 1,
"resolvedCount" : 0
}
]
}
Как только я расширю выбор даты, чтобы включить эту дату, что, конечно, выбор даты также может быть улучшен и исправлен из вашей исходной формы.
Так что, кажется, имеет смысл, что ваши отношения на самом деле определены таким образом, но вы просто записали их «дважды».Вам не нужно, и даже если это не определение, тогда вы должны вместо этого записывать в «потомок», а не в массив в родительском элементе.Мы можем манипулировать и объединять родительские массивы, но это контрпродуктивно для правильного установления отношений данных и их правильного использования.