猫鼬从嵌套的对象数组中获取确切的对象

Mongoose to get the exact object from a nested array of objects

我想使用 mongoose 查询从嵌套对象数组中检索确切的对象。 我已经使用 $elemMatch 尝试了一些查询,但没有得到我要定位的确切对象,而是我得到了对象数组。

我的 BSON 是

{
  "_id": {
    "$oid": "622bcb7a1091ddd45201258e"
  },
  "type": "Humans",
  "person": [
    {
      "name": "Name Person 1",
      "visited": [],
      "_id": {
        "$oid": "622bdjnmyi30e62d6d166ebd"
      }
    },
    {
      "name": "Name Person 2",
      "visited": [
        {
          "country": "Country 1",
          "year": "2022",
          "id": "zuPks8cv3n"
        }
      ],
      "_id": {
        "$oid": "622bdopmLks0e62d6d166ebe"
      }
    },
    {
      "name": "Name Person 3",
      "_id": {
        "$oid": "622bdpo8bnj0e62d6d166ebf"
      },
      "visited": [
        {
          "country": "Country 3",
          "year": "2029",
          "id": "l2Opx489xb"
        },
        {
          "country": "Country 4",
          "year": "2002",
          "id": "s09zbHYjIp"
        }
      ]
    },
    {
      "name": "Name Person 4",
      "visited": [],
      "_id": {
        "$oid": "622bdb9eio0sbt2d6d166ec0"
      }
    }
  ],
  "__v": {
    "$numberInt": "0"
  }
}

我的目标是 “姓名 Person 3” 对象数组 “已访问” 和 ID 为 的对象“l2Opx489xb”.

我的预期结果是:

{
  "country": "Country 3",
  "year": "2029",
  "id": "l2Opx489xb"
}

这是常见问题的一种变体,在这种情况下,尝试匹配数组中的单个项目会产生整个数组。问题不是“匹配”,因为匹配是正确的;这是投影的问题。找到匹配项后,我们只希望投影数组中的特定项目或一般文档的其余部分。

这是一个没有 $unwind 的单级解决方案。通常,在 $filter$map$reduce 之间,可以在没有 $unwind 的情况下从单个文档中提取数据,这可能很昂贵。阅读“inside-out”中以#1 开头的评论。

db.foo.aggregate([
    {$replaceRoot: {newRoot: // #7 ...and make this the root object
        {$first: // #6 like #2, turn the array of 1 into a single object.
            {$filter: {  // #4 ... and now we filter the 'visited' array...
                input: {$let: {
                    vars: {qq: {$first: // #2  $filter will yield an array of 0 or 1;
                                        // use $first to turn into one object
                                // #1 Find Name Person 3
                                {$filter: {
                                input: '$person',
                                cond: {$eq:['$$this.name','Name Person 3']}
                                }}
                           }},
                     //  #3  We wish we could say $first.visited in #2 but we cannot
                     //  so we use $let and the vars setup to allow us to get to
                     //  the inner array 'visited':
                     in: '$$qq.visited'}
                 },
                 cond: {$eq:['$$this.id','l2Opx489xb']} // #5 to match target id
            }}
        }
    }}
]);

{ "country" : "Country 3", "year" : "2029", "id" : "l2Opx489xb" }

注意:$first 到达 v4.4。对于早期版本,而不是

{$first: <expression that yields array>}

改用这个:

{$arrayElemAt: [ <expression that yields array>, 0]

$first 更简洁一些,因为复杂表达式末尾没有 ,0“悬挂”。

下面是“扩展”版本加上针对 $X 评估为 null 的额外检查(如果 Name Person 3 或目标 ID 不存在)因为 null 不能传递给 $replaceRoot:

db.foo.aggregate([
    {$project: {
        X: {$first: {$filter: {
            input: '$person',
            cond: {$eq:['$$this.name','Name Person 3']}
        }} }
    }}

    ,{$project: {
        X: {$first: {$filter: {
            input: '$X.visited',
            cond: {$eq:['$$this.id','l2Opx489xb']}
        }} }
    }}

    ,{$match: {X: {$ne: null}} }

    ,{$replaceRoot: {newRoot: '$X'}}
]);

OP 已将问题扩展为包括 1. 删除 visited 数组中的项目 2. 将对等字段更新为 visited 数组中的 ID 匹配。有两种方法:简单的和探索管道功能的一些强大功能的“艺术”。

简单

要更新年份和国家:

db.foo.update(                                                                         
    {'person.name':'Name Person 3'}                                                       
    ,{$set: {                                                                             
        'person.$[p].visited.$[v].year':'1999',                                           
        'person.$[p].visited.$[v].country':'ZOOP'                                         
    }}                                                                                    
    ,{arrayFilters: [ {'p.name':'Name Person 3'},{'v.id':'l2Opx489xb'} ] }                
); 

要从 visited 数组中完全删除目标 id 对象:

db.foo.update(
    {'person.name':'Name Person 3'}
    ,{$pull: {'person.$.visited': {'id':'l2Opx489xb'}}}
);

ARTSY

rc=db.foo.update(
    // Use dot notation for match to cut down initial input set.  
    // Remember:  This will NOT find the exact offset into the
    // array where Name Person 3 may be found, only that it is 
    // *somewhere* in that array.  But this is good to filter out
    // things where NO update is needed.  The pipeline below will
    // hunt for and remove the target id.
    {'person.name':'Name Person 3'},

    [{$set: {  // pipeline form of update
    // Use $reduce to walk the person array and essentially
    // rebuild it but only operating on those entries where
    // name is 'Name Person 3'
    person: {$reduce: {
        input: '$person',
        initialValue: [],

        // $concatArrays wants 2 arrays.  The first is our ever-growing
        // $$value.  The second is a candidate object -- but we have to
        // turn THAT into an array of 1, so watch for the [ ] wrapper around
        // the outer $cond expression.
        in:{$concatArrays: [ "$$value", [
          {$cond: [
            {$ne:['$$this.name','Name Person 3']}, // if NOT our target
            '$$this', // THEN pass to concatArrays unchanged (noop)

            // ELSE

            // To DELETE the entire object:
            // overwrite 'visited' with filtered version
            // of same.  This lets all other vars like name
            // and anything else pass thru unchanged.
            {$mergeObjects: [ '$$this', 
                      {visited: {$filter: {
                      input: '$$this.visited',
                      as: 'qq',
                      cond: {$ne:['$$qq.id','l2Opx489xb']}
                      }}
              }
            ]}

            /*
              To UPDATE year and country in the target, use
              this instead instead of the $mergeObjects call
              just above.  Choose one or the other; both cannot
              be active at same time.

            {$mergeObjects: [ '$$this', 
                  {visited: {$map: {  // $map this time, not $filter
                  input: '$$this.visited',
                  as: 'qq',
                  in: {$cond:[
                      {$ne:['$$qq.id','l2Opx489xb']},
                      '$$qq', // THEN return unchanged

                      // ELSE overwrite year and country
                      // fields of the object:
                      {$mergeObjects: [ '$$qq', 
                            {year:"1999",
                             country:"ZPONC"}
                              ]}
                  ]}
                  }}
                  }
            ]}
            */
         ]}
        ]] }
    }}
    }}
    ]
    // ,{multi:true}  // if Name Person 3 exists in more than 1 doc....
);