Supposons que je dispose de quelques documents sur les événements MongoDB, chacun ayant un certain nombre de sessions qui ont lieu à des dates différentes. Nous pourrions représenter cela comme suit:
Event.aggregate([
{ $unwind: '$sessions' }, {
$group: {
_id: '$_id',
startDate: { $min: '$sessions.date' }
}
},
{ $sort:{ startDate: 1 } }, {
$match: { startDate: { $gte: new Date() } }
}
])
.then(result => Event.find({ _id: result.map(result => result._id) }))
.then(event => Event.populate(events, 'host'))
.then(events => res.json(events))
J'aimerais trouver tous les événements qui n'ont pas encore eu leur première session. Donc j'aimerais trouver "Future" mais pas "Past".
En ce moment, j'utilise Mongoose et Express pour faire:
db.events.insert([
{
_id: '5be9860fcb16d525543cafe1',
name: 'Past',
host: '5be9860fcb16d525543daff1',
sessions: [
{ date: new Date(Date.now() - 1e8 ) },
{ date: new Date(Date.now() + 1e8 ) }
]
}, {
_id: '5be9860fcb16d525543cafe2',
name: 'Future',
host: '5be9860fcb16d525543daff2',
sessions: [
{ date: new Date(Date.now() + 2e8) },
{ date: new Date(Date.now() + 3e8) }
]
}
]);
Mais j'ai l'impression de faire du gros temps. Deux résultats sur la base de données (trois si vous incluez l'instruction populate ) et une instruction aggregate volumineuse et compliquée.
Existe-t-il un moyen plus simple de faire ce? Idéalement, celui qui ne comporte qu'un seul voyage dans la base de données.
3 Réponses :
Vous pouvez utiliser $ réduire code > pour plier le tableau et rechercher si l'un des éléments a une session passée.
Pour illustrer cela, envisagez d'exécuter le pipeline agrégé suivant:
{
"_id" : "5be9860fcb16d525543cafe2",
"name" : "Future",
"host" : "5be9860fcb16d525543daff2",
"sessions" : [
{
"date" : ISODate("2019-01-06T23:24:36.174Z")
},
{
"date" : ISODate("2019-01-08T03:11:16.174Z")
}
]
}
Sur la base de l'exemple ci-dessus, cela produira les documents suivants avec le champ supplémentaire
XXX
Armé de ce pipeline agrégé, vous pouvez ensuite tirer parti de $ expr et utilisez l'expression de pipeline comme requête dans la find () méthode (ou en utilisant la méthode opération d'agrégation ci-dessus mais avec $ match code> étape du pipeline à la fin activée) comme
db.events.find(
{ "$expr": {
"$eq": [
false,
{ "$reduce": {
"input": "$sessions.date",
"initialValue": false,
"in": {
"$or" : [
"$$value",
{ "$lt": ["$$this", new Date()] }
]
}
} }
]
} }
)
qui renvoie le document
/* 1 */
{
"_id" : "5be9860fcb16d525543cafe1",
"name" : "Past",
"host" : "5be9860fcb16d525543daff1",
"sessions" : [
{
"date" : ISODate("2019-01-03T12:04:36.174Z")
},
{
"date" : ISODate("2019-01-05T19:37:56.174Z")
}
],
"hasPastSession" : true
}
/* 2 */
{
"_id" : "5be9860fcb16d525543cafe2",
"name" : "Future",
"host" : "5be9860fcb16d525543daff2",
"sessions" : [
{
"date" : ISODate("2019-01-06T23:24:36.174Z")
},
{
"date" : ISODate("2019-01-08T03:11:16.174Z")
}
],
"hasPastSession" : false
}
Je ne sais pas pourquoi cela a répondu à la question car la requête de l'OP fonctionne, mais l'OP a utilisé trois requêtes et a besoin d'une seule requête pour la simplifier. 1) La première requête d'Op avec agrégat fonctionne. 2) Op ne connaît pas $ first dans l'agrégation de groupe, c'est pourquoi il a utilisé la deuxième requête pour trouver à nouveau la clé host . 3) Utilisé populate car Op ne connaît pas $ lookup . Mais je ne vois rien qui y soit lié. Ai-je raison?
@AnthonyWinzlet $ unwind a un énorme coût en termes de performances, en particulier le déroulement de tous les documents de la collection avant de faire correspondre le ou les documents spécifiques que vous voulez réellement. La raison pour laquelle $ unwind n'est pas aussi efficace est qu'il produit un produit cartésien des documents c'est-à-dire une copie de chaque document par entrée de tableau, qui utilise plus de mémoire (plafond de mémoire possible sur les pipelines d'agrégation de 10% mémoire totale) et prend donc du temps à produire ainsi que le traitement des documents pendant le processus d'aplatissement. La réponse ci-dessus élimine ce goulot d'étranglement avec une seule requête
OK, mais je ne vois toujours aucune jointure pour connecter le champ hôte .
Eh bien, la question principale d’OP est Je voudrais trouver tous les événements qui n’ont pas encore eu leur première session. Donc j'aimerais trouver 'Future' mais pas 'Past'. , le remplissage de host peut simplement être fait en enchaînant la méthode find ()
Est-il possible que vous puissiez simplement accéder aux sessions de chaque événement et retirer chaque événement où toutes les dates de session ne sont que dans le futur? Quelque chose comme ça? Peut-être besoin d'être peaufiné ..
db.getCollection("events").aggregate(
[
{$match:{'$and':
[
{'sessions.date':{'$gt': new Date()}},
{'sessions.date':{ '$not': {'$lt': new Date()}}}
]
}}
]
);
Vous n'avez pas besoin d'utiliser $ dérouler et $group pour trouver la date $ min dans le tableau. Vous pouvez utiliser directement $ min pour extraire la date minimale du tableau session , puis utilisez $ lookup pour renseigner la clé host
db.events.aggregate([
{ "$match": { "sessions.date": { "$gte": new Date() }}},
{ "$addFields": { "startDate": { "$min": "$sessions.date" }}},
{ "$match": { "startDate": { "$gte": new Date() }}},
{ "$lookup": {
"from": "host",
"localField": "host",
"foreignField": "_id",
"as": "host"
}},
{ "$unwind": "$host" }
])