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" } ])