【问题标题】:Check If field exists in an sub-document of an Array检查字段是否存在于数组的子文档中
【发布时间】:2015-11-27 10:08:15
【问题描述】:

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

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

【问题讨论】:

    标签: mongodb mongoose mongodb-query


    【解决方案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
    }
    

    因此,下次您在代码中向数组中添加新项目时,如果未明确设置,该元素将始终以其默认值存在。这样做可能是一个好主意,同时将required 设置在您一直想要的其他字段上,例如“id”。

    【讨论】:

    • 答案对我来说没问题。我很想为数据设置默认值,但它是从另一个外部系统中提取并从 python 插入的。那时,我们不想在那里进行任何数据处理。我只处理来自我的 nodeJs 的数据。我需要进一步的帮助。如果我想 更新 id 为 3 且 line_item 对象 id 为 85 的对象。即文档 id 为 3,line_item 数组为 85
    • $elemMatch 仅返回第一个匹配项。如果你想匹配更多怎么办?
    • 这对我有用db.history.find({"treat":{ "$elemMatch": {"nameOfPerson":{"$exists": true}}}})
    • 完美答案。对我遇到的“拉头发”类型的问题进行了分类。谢谢
    【解决方案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 数组包含值的文档,即 $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 与未定义字段一起使用时返回“缺失”。

      【讨论】:

        【解决方案4】:
        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"
                    }
                }
            }
        }])
        

        【讨论】:

          猜你喜欢
          • 2019-03-04
          • 2018-08-16
          • 2023-03-07
          • 2016-09-09
          • 2017-09-28
          • 2022-01-24
          • 1970-01-01
          • 1970-01-01
          • 2022-11-02
          相关资源
          最近更新 更多