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

Document de retour avec sous-document max.

Tout d'abord, montrons vos données de manière à ce que les gens puissent les utiliser et produire le résultat souhaité :

{ value: 1,
  _id: ObjectId('5cb9ea0c75c61525e0176f96'),
  name: 'Test',
  category: 'Development',
  subcategory: 'Programming Languages',
  status: 'Supported',
  description: 'Test',
  change:
   [ { version: 1,
       who: 'ATL User',
       when: new Date('2019-04-19T15:30:39.912Z'),
       what: 'Item Creation' },
     { version: 2,
       who: 'ATL Other User',
       when: new Date('2019-04-19T15:31:39.912Z'),
       what: 'Name Change' } ],
}

Notez que le "when" les dates sont en fait différentes donc il y aura un $max valeur et ils ne sont pas simplement les mêmes. Nous pouvons maintenant passer en revue les cas

Cas 1 - Récupérer le "singulier" $max valeur

Le cas de base ici est d'utiliser le $arrayElemAt et $indexOfArray opérateurs pour renvoyer le $max correspondant valeur :

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$arrayElemAt": [
        "$change",
        { "$indexOfArray": [
          "$change.when",
          { "$max": "$change.when" }
        ]}
      ]
    }
  }}
])

Renvoie :

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : {
                "version" : 2,
                "who" : "ATL Other User",
                "when" : ISODate("2019-04-19T15:31:39.912Z"),
                "what" : "Name Change"
        }
}

Fondamentalement, le "$max": "$change.when" renvoie la valeur qui est le "maximum" à partir de ce tableau de valeurs. Vous trouvez ensuite "l'index" correspondant de ce tableau de valeurs via $indexOfArray qui renvoie le premier index correspondant trouvé. Cette position "index" (à partir en fait d'un tableau de "when" valeurs transposées dans le même ordre) est ensuite utilisé avec $arrayElemAt pour extraire "l'objet entier" du "change" tableau à la position d'index spécifiée.

Cas 2 - Renvoie le "multiple" $max entrées

À peu près la même chose avec $max , sauf que cette fois nous $filter pour retourner le multiple "possible" valeurs correspondant à $max valeur :

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$filter": {
        "input": "$change",
        "cond": {
          "$eq": [ "$$this.when", { "$max": "$change.when" } ]
        }
      }       
    }
  }}
])

Renvoie :

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
}

Ainsi, le $max est bien sûr le même mais cette fois la valeur singulière renvoyée par cet opérateur est utilisée dans un $eq comparaison dans $filter . Cela inspecte chaque élément du tableau et regarde le courant "when" valeur ( "$$this.when" ). Où "égal" alors l'élément est retourné.

Fondamentalement la même que la première approche mais à l'exception que $filter autorise "multiple" éléments à restituer. Donc tout avec le même $max valeur.

Cas 3 - Pré-trier le contenu du tableau.

Maintenant, vous remarquerez peut-être que dans l'exemple de données que j'ai inclus (adapté des vôtres mais avec une date "max" réelle), la valeur "max" est en fait la dernière valeur dans le tableau. Cela peut se produire naturellement car $push ( par défaut ) "ajoute" à la fin du contenu du tableau existant. Donc "plus récent" les entrées auront tendance à être à la fin du tableau.

C'est bien sûr la valeur par défaut comportement, mais il y a de bonnes raisons pour lesquelles vous "pouvez" veux changer ça. Bref le meilleur moyen d'obtenir le "plus récent" entrée de tableau est en fait de retourner le premier élément du tableau.

Tout ce que vous avez à faire est de vous assurer que le "plus récent" est en fait ajouté en premier plutôt que dernier . Il existe deux approches :

  1. Utilisez $position pour "pré-mettre en attente" les éléments du tableau : Il s'agit d'un simple modificateur de $push en utilisant le 0 poste afin de toujours ajouter au devant :

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [{
              "version": 3,
              "who": "ATL User",
              "when": new Date(),
              "what": "Another change"
            }],
            "$position": 0
          }
       }}
    )
    

    Cela changerait le document en :

    {
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                },
                {
                        "version" : 1,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-19T15:30:39.912Z"),
                        "what" : "Item Creation"
                },
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
    }
    

Notez que cela vous obligerait à aller "inverser" tous les éléments de votre tableau au préalable afin que le "plus récent" soit déjà à l'avant afin que l'ordre soit maintenu. Heureusement, cela est quelque peu couvert dans la deuxième approche...

  1. Utilisez $sort pour modifier les documents dans l'ordre sur chaque $push : Et c'est l'autre modificateur qui "re-trie" de manière atomique à chaque nouvel ajout d'élément. L'utilisation normale est fondamentalement la même avec tous les nouveaux éléments à $each comme ci-dessus, ou même juste un tableau "vide" afin d'appliquer le $sort aux données existantes uniquement :

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [],
            "$sort": { "when": -1 } 
          }
       }}
    )
    

    Résultats en :

    {
            "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
            "value" : 1,
            "name" : "Test",
            "category" : "Development",
            "subcategory" : "Programming Languages",
            "status" : "Supported",
            "description" : "Test",
            "change" : [
                    {
                            "version" : 3,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-20T02:40:30.024Z"),
                            "what" : "Another change"
                    },
                    {
                            "version" : 2,
                            "who" : "ATL Other User",
                            "when" : ISODate("2019-04-19T15:31:39.912Z"),
                            "what" : "Name Change"
                    },
                    {
                            "version" : 1,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-19T15:30:39.912Z"),
                            "what" : "Item Creation"
                    }
            ]
    }
    

    Cela peut prendre une minute pour comprendre pourquoi vous voulez $push afin de $sort un tableau comme celui-ci, mais l'intention générale est lorsque des modifications peuvent être apportées à un tableau qui "modifie" une propriété comme une Date valeur triée et vous utiliseriez une telle instruction pour refléter ces modifications. Ou bien ajoutez simplement de nouveaux éléments avec le $sort et laissez faire.

Alors pourquoi "stocker" le tableau commandé comme ça? Comme mentionné précédemment, vous voulez le premier élément comme "le plus récent" , puis la requête à renvoyer qui devient simplement :

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true }
  },
  { "change": { "$slice": 1 } }
)

Renvoie :

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                }
        ]
}

Donc le $slice peut ensuite être utilisé uniquement pour extraire des éléments de tableau par des index connus. Techniquement, vous pouvez simplement utiliser -1 là pour retourner le dernier élément du tableau de toute façon, mais la réorganisation où le plus récent est le premier permet d'autres choses comme la confirmation que la dernière modification a été faite par un certain utilisateur, et/ou d'autres conditions comme une contrainte de plage de dates. c'est-à-dire :

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true },
    "change.0.who": "ATL User",
    "change.0.when": { "$gt": new Date("2018-04-01") }
  },
  { "change": { "$slice": 1 } }
)

Notant ici que quelque chose comme "change.-1.when" est une déclaration illégale, c'est pourquoi nous réorganisons le tableau afin que vous puissiez utiliser le legal 0 pour premier au lieu de -1 pour dernier .

Conclusion

Il y a donc plusieurs choses que vous pouvez faire, soit en utilisant l'approche d'agrégation pour filtrer le contenu du tableau, soit via des formulaires de requête standard après avoir apporté quelques modifications à la manière dont les données sont réellement stockées. Lequel utiliser dépend de votre propre situation, mais il convient de noter que l'un des formulaires de requête standard s'exécutera nettement plus rapidement que toute manipulation via le cadre d'agrégation ou tout opérateur calculé.