Check If field exists in an sub-document of an Array

后端 未结 4 538
后悔当初
后悔当初 2021-02-12 15:38

I have a schema that is similar to this.

{id: Number,
  line_items: [{ 
    id: String,
    quantity: Number,
    revi         


        
4条回答
  •  爱一瞬间的悲伤
    2021-02-12 16:32

    You basically want $elemMatch and the $exists operator, as this will inspect each element to see if the condition "field not exists" is true for any element:

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

    That returns the second document only as the field is not present in one of the array sub-documents:

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

    Note that this "differs" from this form:

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

    Where that is asking does "all" of the array elements not contain this field, which is not true when a document has at least one element that has the field present. So the $eleMatch makes the condition be tested against "each" array element, and thus you get the correct response.


    If you wanted to update this data so that any array element found that did not contain this field was to then receive that field with a value of false ( presumably ), then you could even write a statement like this:

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

    Where the .aggregate() result not only matches the documents, but filters out content from the array where the field was not present, so as to just return the "id" of that particular sub-document.

    Then the looped .update() statements match each found array element in each document and adds the missing field with a value at the matched position.

    In that way you will have the field present in all sub-documents of every document where it was missing before.

    If you want to do such a thing, then it would also be wise to change your schema to make sure the field is always there as well:

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

    So the next time you add new items to the array in your code the element will always exist with it's default value if not otherwise explicitly set. And it is probably a goood idea to do so, as well as setting required on other fields you always want, such as "id".

提交回复
热议问题