Проверить Если в поддоке массива есть поле

У меня есть схема, которая похожа на это.

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

Я хочу найти все документы, для которых не установлено поле review_request_sent для всех элементов в массиве.

пример

{ 
  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
    }
  ]
}

Я хотел бы помочь с запросом/находкой, который вернет только второй документ, потому что у него нет поля review_request_sent во всех его элементах в его массиве.

Ответ 1

В основном вы хотите $elemMatch и $exists, так как это проверит каждый элемент, чтобы убедиться, что условие "поле не существует" истинно для любого элемента:

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

});

Это возвращает второй документ только в том случае, если поле не присутствует в одном из поддокументов массива:

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

Обратите внимание, что это "отличается" от этой формы:

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

})

В этом вопросе "все" элементов массива не содержат этого поля, что неверно, если в документе есть хотя бы один элемент, в котором есть данное поле. Таким образом, параметр $eleMatch проверяет условие на элемент "каждый", и вы получаете правильный ответ.


Если вы хотите обновить эти данные, чтобы любой найденный элемент массива, который не содержал это поле, должен был получить это поле со значением false (предположительно), тогда вы могли бы даже написать инструкцию следующим образом:

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

Где результат .aggregate() не только совпадает с документами, но и фильтрует содержимое из массива, где это поле отсутствует, чтобы просто вернуть "id" этого конкретного поддоку.

Затем петлевые операторы .update() соответствуют каждому найденному элементу массива в каждом документе и добавляют недостающее поле со значением в согласованной позиции.

Таким образом, у вас будет поле, присутствующее во всех поддокументах каждого документа, где он отсутствовал раньше.

Если вы хотите сделать что-то подобное, тогда было бы разумно изменить вашу схему, чтобы убедиться, что поле всегда там:

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

Итак, в следующий раз, когда вы добавите новые элементы в массив в вашем коде, элемент всегда будет существовать с его значением по умолчанию, если иное не задано явно. И это, вероятно, идея goood для этого, а также установка required в другие поля, которые вы всегда хотите, такие как "id".

Ответ 2

Вы можете найти это другим способом без $map и $unwind, используя $redact следующим образом:

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

В предыдущем запросе сначала match проверьте, присутствует ли review_request_sent или нет в redact $ifNull, чтобы проверить, review_request_sent представляет или нет, если нет, то он заменяется на "null" и $eq, чтобы проверить "null", это вернет документы типа

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

и после этого последнего совпадения проверяют только те документы, чей массив line_items содержит значение i.e $size до 1

Ответ 3

Если вы просто хотите получить весь документ, то, как уже упоминалось @Blakes Seven. Вы можете запросить, используя $ elemMatch.
Но если вы хотите отфильтровать элемент массива и получить только те элементы массива, которые не имеют определенного поля, вы можете использовать $ filter вместе с $ type в конвейере агрегации.

[
    { $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" ] }
        } }
    } }
]

Примечание: $ type возвращает "отсутствует" при использовании с неопределенными полями.