web-dev-qa-db-fra.com

Vérifier si le champ existe dans un sous-document d'un tableau

J'ai un schéma qui ressemble à ceci. 

{id: Number,
  line_items: [{ 
    id: String,
    quantity: Number,
    review_request_sent :Boolean
  }],
  total_price: String,
  name: String,
  order_number: Number
}

Ce que je veux trouver, ce sont tous les éléments qui n'ont pas défini le champ review_request_sent pour tous les éléments du tableau.

Exemple 

{ 
  id: 1, 
  line_items: [
    {
      id: 43,
      review_request_sent: true
    }
  ]
},
{
  id: 2,
  line_items: [
    {
      id: 1,
      review_request_sent: false
    },
    {
      id: 39
    },
 ]
},
{
  id: 3,
  line_items: [
    {
     id: 23,
     review_request_sent: true
    },
    {
     id: 85,
     review_request_sent: true
    },
    {
     id: 12,
     review_request_sent: false
    }
  ]
}

Je voudrais de l'aide avec une requête/find qui ne renverra que le document seccond car il n'a pas le champ review_request_sent dans tous ses éléments de son tableau.

13
user841818

Vous voulez fondamentalement $elemMatch et l'opérateur $exists , car cela inspectera chaque élément pour voir si la condition "champ n'existe pas" est vraie pour tout élément:

Model.find({
  "line_items": {
      "$elemMatch": { "review_request_sent": { "$exists": false } }
  }
},function(err,docs) {

});

Cela renvoie le deuxième document uniquement car le champ n'est pas présent dans l'un des sous-documents du tableau:

{
        "id" : 2,
        "line_items" : [
                {
                        "id" : 1,
                        "review_request_sent" : false
                },
                {
                        "id" : 39
                }
        ]
}

Notez que cela "diffère" de cette forme:

Model.find({
  "line_items.review_request_sent": { "$exists": false } 
},function(err,docs) {

})

Là où cela est demandé, "tous" des éléments du tableau ne contiennent pas ce champ, ce qui n'est pas vrai lorsqu'un document a au moins un élément dont le champ est présent. Donc, le $eleMatch fait en sorte que la condition soit testée par rapport à "chaque" élément de tableau et vous obtenez ainsi la réponse correcte.


Si vous souhaitez mettre à jour ces données de manière à ce que tout élément de tableau trouvé ne contenant pas ce champ reçoive ensuite ce champ avec une valeur de false (probablement), vous pouvez même écrire une instruction comme celle-ci:

    Model.aggregate(
      [
        { "$match": { 
          "line_items": {
            "$elemMatch": { "review_request_sent": { "$exists": false } }
          } 
        }},
        { "$project": {
          "line_items": {
            "$setDifference": [
              {"$map": {
                "input": "$line_items",
                "as": "item",
                "in": {
                  "$cond": [
                    { "$eq": [ 
                      { "$ifNull": [ "$$item.review_request_sent", null ] },
                      null
                    ]},
                    "$$item.id",
                    false
                  ]
                }
              }},
              [false]
            ]
          }
        }}
    ],
    function(err,docs) {
      if (err) throw err;
      async.each(
        docs,
        function(doc,callback) {
          async.each(
            doc.line_items,
            function(item,callback) {
              Model.update(
                { "_id": doc._id, "line_items.id": item },
                { "$set": { "line_items.$.review_request_sent": false } },
                callback
              );
            },
            callback
          );
        },
        function(err) {
          if (err) throw err;
          // done
        }
      );
    }
  );

Où le résultat .aggregate() correspond non seulement aux documents, mais filtre le contenu du tableau où le champ n'était pas présent, afin de simplement renvoyer "l'identifiant" de ce sous-document particulier.

Ensuite, les instructions en boucle .update() correspondent à chaque élément de tableau trouvé dans chaque document et ajoutent le champ manquant avec une valeur à la position correspondante.

De cette façon, vous aurez le champ présent dans tous les sous-documents de chaque document où il manquait auparavant.

Si vous voulez faire une telle chose, alors il serait également sage de changer votre schéma pour vous assurer que le champ est toujours là aussi:

{id: Number,
  line_items: [{ 
    id: String,
    quantity: Number,
    review_request_sent: { type: Boolean, default: false }
  }],
  total_price: String,
  name: String,
  order_number: Number
}

Ainsi, la prochaine fois que vous ajouterez de nouveaux éléments au tableau dans votre code, l'élément existera toujours avec sa valeur par défaut s'il n'est pas défini explicitement autrement. Et c’est probablement une bonne idée de le faire, ainsi que de définir required sur d’autres champs de votre choix, tels que "id".

18
Blakes Seven

Vous pouvez trouver cela d'une autre manière sans que $map et $unwind soient utilisés $ rédiger comme ceci:

db.collectionName.aggregate({
  "$match": {
    "line_items.review_request_sent": {
      "$exists": true
    }
  }
}, {
  "$redact": {
    "$cond": {
      "if": {
        "$eq": [{
          "$ifNull": ["$review_request_sent", "null"]
        }, "null"]
      },
      "then": "$$DESCEND",
      "else": "$$Prune"
    }
  }
}, {
  "$match": {
    "line_items": {
      "$size": 1
    }
  }
}).pretty()

Dans la requête ci-dessus, la première match vérifie si review_request_sent est présent ou non dans redact$ ifNull use pour vérifier si review_request_sent est présent ou non, il a été remplacé par "null" et $eq pour vérifier "null", ce qui renverra des documents comme 

{ "_id" : ObjectId("55e6ca322c33bb07ff2c163e"), "id" : 1, "line_items" : [ ] }
{ "_id" : ObjectId("55e6ca322c33bb07ff2c163f"), "id" : 2, "line_items" : [ { "id" : 39 } ] }
{ "_id" : ObjectId("55e6ca322c33bb07ff2c1640"), "id" : 3, "line_items" : [ ] }

et après cette dernière correspondance, cochez uniquement les documents dont le tableau line_items contient la valeur i.e $ size to 1 

2
Yogesh

Si vous voulez juste obtenir le document entier, comme mentionné par @Blakes Seven . Vous pouvez interroger avec $ elemMatch .
Mais si vous voulez filtrer un élément de tableau et obtenir uniquement les éléments de tableau qui ne possèdent pas un champ donné, vous pouvez utiliser $ filter avec le pipeline d'agrégation $ type in.

[
    { $match: { "line_items": { "$elemMatch": { "review_request_sent": { "$exists": false } } } } },
    { $project: {
        "line_items": { "$filter": { 
            "input": "$line_items", 
            "as": "item", 
            "cond": { "$eq": [ { "$type": "$$item.review_request_sent" }, "missing" ] }
        } }
    } }
]

Remarque:$ type renvoie "manquant" lorsqu'il est utilisé avec des champs non définis.

1