3
votes

Rechercher les documents à venir en fonction de la date du sous-document

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.


0 commentaires

3 Réponses :


3
votes

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
}


4 commentaires

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 ()



0
votes

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()}}} 
               ]
             }}
    ]
);


0 commentaires

1
votes

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


0 commentaires