MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Conditions de correspondance et dernière date du tableau

Le concept de base ici est que vous avez besoin du cadre d'agrégation afin d'appliquer des conditions pour "filtrer" les éléments du tableau aux conditions. Selon la version disponible, différentes techniques peuvent être appliquées.

Dans tous les cas voici le résultat :

{
    "_id" : ObjectId("593921425ccc8150f35e7664"),
    "user1" : 1,
    "user2" : 4,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-09T10:04:50Z"),
            "body" : "hiii 1"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7663"),
    "user1" : 1,
    "user2" : 3,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-10T10:04:50Z"),
            "body" : "hiii 2"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7662"),
    "user1" : 1,
    "user2" : 2,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-08T10:04:50Z"),
            "body" : "hiii 0"
    }
}

MongoDB 3.4 et supérieur

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$replaceRoot": {
    "newRoot": {
      "$let": {
        "vars": {
          "messages": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "maxDate": {
            "$max": {
              "$map": {
                "input": {
                  "$filter": {
                    "input": "$messages",
                    "as": "m",
                    "cond": { "$eq": [ "$$m.sender", 1 ] }
                  }
                },
                "as": "m",
                "in": "$$m.datetime"
              }
            }
          }
        },
        "in": {
          "_id": "$_id",
          "user1": "$user1",
          "user2": "$user2",
          "messages": {
            "$arrayElemAt": [
              { "$filter": {
                "input": "$$messages",
                "as": "m",
                "cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
              }},
              0
            ]
          }    
        }
      }
    }
  }}
])

C'est le moyen le plus efficace qui tire parti de $replaceRoot qui nous permet de déclarer des variables à utiliser dans la structure "remplacée" en utilisant $let . Le principal avantage ici est que cela ne nécessite que "deux" étapes de pipeline.

Afin de faire correspondre le contenu du tableau, vous utilisez $filter où vous appliquez le $eq opération logique pour tester la valeur de "sender" . Lorsque la condition correspond, seules les entrées de tableau correspondantes sont renvoyées.

Utiliser le même $filter afin que seules les entrées "expéditeur" correspondantes soient prises en compte, nous voulons ensuite appliquer $max sur la liste "filtrée" aux valeurs dans "datetime" . Le $max ]5 la valeur est la "dernière" date selon les conditions.

Nous voulons cette valeur afin de pouvoir comparer ultérieurement les résultats renvoyés par le tableau "filtré" à ce "maxDate". C'est ce qui se passe dans le "in" bloc de $let où les deux "variables" déclarées précédemment pour le contenu filtré et le "maxDate" sont à nouveau appliqués à $filter afin de renvoyer ce qui devrait être la seule valeur remplissant les deux conditions ayant également la "dernière date".

Puisque vous ne voulez qu'"un" résultat, nous utilisons $arrayElemAt pour utiliser la valeur plutôt que le tableau.

MongoDB 3.2

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$filter": {
        "input": "$messages",
        "as": "m",
        "cond": { "$eq": [ "$$m.sender", 1 ] }
      }
    },
    "maxDate": {
      "$max": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "as": "m",
          "in": "$$m.datetime"
        }
      }
    }         
  }},
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$arrayElemAt":[
       { "$filter": {
         "input": "$messages",
          "as": "m",
          "cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
       }},
       0
      ]
    }
  }}
])

Il s'agit essentiellement du même processus que celui décrit, mais sans le $replaceRoot étape du pipeline, nous devons postuler en deux $project étapes. La raison en est que nous avons besoin de la "valeur calculée" de "maxDate" afin de faire ce dernier $filter , et il n'est pas possible de le faire dans une instruction composée, donc à la place, nous divisons les pipelines. Cela a un faible impact sur le coût global de l'opération.

Dans MongoDB 2.6 à 3.0, nous pouvons utiliser la plupart des techniques ici, à l'exception de $arrayElemAt et soit accepter le résultat "tableau" avec une seule entrée ou mettre dans un $unwind étape pour traiter ce qui devrait maintenant être une entrée unique.

Versions antérieures de MongoDB

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$unwind": "$messages" },
  { "$match": { "messages.sender": 1 } },
  { "$sort": { "_id": 1, "messages.datetime": -1 } },
  { "$group": {
    "_id": "$_id",
    "user1": { "$first": "$user1" },
    "user2": { "$first": "$user2" },
    "messages": { "$first": "$messages" }
  }}
])

Bien qu'elle paraisse brève, c'est de loin l'opération la plus coûteuse. Ici, vous devez utiliser $unwind afin d'appliquer les conditions aux éléments du tableau. C'est un processus très coûteux car il produit une copie de chaque document pour chaque entrée de tableau, et est essentiellement remplacé par les opérateurs modernes qui évitent cela dans le cas du "filtrage".

Le deuxième $match stage ici supprime tous les éléments (maintenant "documents" ) qui ne correspondaient pas à la condition "sender". Ensuite, nous appliquons un $sort afin de mettre la "dernière" date en haut pour chaque document par le _id , d'où les deux clés de "tri".

Enfin nous appliquons $group afin de simplement se référer au document original, en utilisant $first comme accumulateur pour obtenir l'élément qui est "sur le dessus".