检查数组的子文档中是否存在字段

时间:2015-09-02 03:05:07

标签: mongodb mongoose mongodb-query

我有一个与此类似的架构。

{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字段。

3 个答案:

答案 0 :(得分:19)

您基本上需要$elemMatch$exists运算符,因为这将检查每个元素以查看条件“field not 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
}

因此,下次在代码中向数组中添加新项时,如果没有另外明确设置,则元素将始终以其默认值存在。这样做可能是个好主意,并且可以在您经常需要的其他字段上设置required,例如“id”。

答案 1 :(得分:2)

您可以在不使用package main import ( "crypto/rand" "encoding/base64" ) // GenerateRandomBytes returns securely generated random bytes. // It will return an error if the system's secure random // number generator fails to function correctly, in which // case the caller should not continue. func GenerateRandomBytes(n int) ([]byte, error) { b := make([]byte, n) _, err := rand.Read(b) // Note that err == nil only if we read len(b) bytes. if err != nil { return nil, err } return b, nil } // GenerateRandomString returns a URL-safe, base64 encoded // securely generated random string. func GenerateRandomString(s int) (string, error) { b, err := GenerateRandomBytes(s) return base64.URLEncoding.EncodeToString(b), err } func main() { // Example: this will give us a 44 byte, base64 encoded output token, err := GenerateRandomString(32) if err != nil { // Serve an appropriately vague error to the // user, but log the details internally. } } $map $redact 的情况下找到另一种方式:

$unwind

在上面的查询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 $ifNull中是否存在review_request_sent用于检查redact是否存在,如果不存在则已更换为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" : [ ] } 数组包含值$size到1

的文档

答案 2 :(得分:0)

let rankers = [
                    {
                        "_id": "5e4d4a13e0eb7b0733f27a18",
                        "totalMarks": 400,
                        "correct": 78,
                        "incorrect": 22,
                        "obtainMarks": 290,
                        "attemptCount": 100,
                        "timeTaken": 0,
                        "rank": 0,
                        "userRank": 1
                    },
                    {
                        "_id": "5e4d4a13e0eb7b0733f27a18",
                        "totalMarks": 400,
                        "correct": 77,
                        "incorrect": 21,
                        "obtainMarks": 287,
                        "attemptCount": 98,
                        "rank": 0,
                        "userRank": 2
                    },
                    {
                        "_id": "5e4d4a13e0eb7b0733f27a18",
                        "totalMarks": 400,
                        "correct": 76,
                        "incorrect": 21,
                        "obtainMarks": 283,
                        "attemptCount": 97,
                        "timeTaken": 0,
                        "userRank": 3
                    }
    ]

在rankers集合中,rank和timeTaken密钥在某些文档中丢失,所以请

Results.aggregate([
{
    $project: {
        "totalMarks": 1,

        "correct": "$correct",
        "incorrect": "$incorrect",
        "obtainMarks": "$obtainMarks",
        "attemptCount": "$attemptCount",
        "timeTaken": {
            $cond: {
                if: { "$eq": [{ "$type": "$timeTaken" }, "missing"] },
                then: 0,
                else: "$timeTaken"
            }
        },
        "rank": {
            $cond: {
                if: { "$eq": [{ "$type": "$rank" }, "missing"] },
                then: 0,
                else: "$rank"
            }
        }
    }
}])