将条件匹配的字段添加到嵌套数组

Posted

技术标签:

【中文标题】将条件匹配的字段添加到嵌套数组【英文标题】:add fields where condition match to nested array 【发布时间】:2018-10-27 08:18:10 【问题描述】:

我有关注users 收藏

[
    "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "firstName" : "bruce",
    "friends" : [ ObjectId("5afd1c42af18d985a06ac306"),ObjectId("5afd257daf18d985a06ac6ac") ]
,

    "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
    "firstName" : "clerk",
    "friends" : [],
]

并拥有friends 收藏

[
    "_id" : ObjectId("5afd1c42af18d985a06ac306"),
    "recipient" : ObjectId("5afaab572c4ec049aeb0bcba"),
    "requester" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "status" : 2,
,

    "_id" : ObjectId("5afd257daf18d985a06ac6ac"),
    "recipient" : ObjectId("5afadfdf08a7aa6f1a27d986"),
    "requester" : ObjectId("5afbfe21daf4b13ddde07dbe"),
    "status" : 1,
]

假设我有一个用户使用_id: "5afaab572c4ec049aeb0bcba" 登录并且这个_id 匹配friendsrecipient

现在我必须添加一个字段friendsStatus,其中包含来自friends 集合的status...如果不匹配数组中的任何recipient,那么它的状态应该是0

所以当我得到所有用户时,我的输出应该是

[
        "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
        "firstName" : "bruce",
        "friends" : [ ObjectId("5afd1c42af18d985a06ac306") ],
        "friendStatus": 2
,

        "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
        "firstName" : "clerk",
        "friends" : [],
        "friendStatus": 0
]

提前致谢!!!

【问题讨论】:

【参考方案1】:

如果您有 MongoDB 3.6,那么您可以将 $lookup 与“子管道”一起使用

User.aggregate([
   "$lookup": 
    "from": Friend.collection.name,
    "let":  "friends": "$friends" ,
    "pipeline": [
       "$match": 
        "recipient": ObjectId("5afaab572c4ec049aeb0bcba"),
        "$expr":  "$in": [ "$_id", "$$friends" ] 
      ,
       "$project":  "status": 1  
    ],
    "as": "friends"
  ,
   "$addFields": 
    "friends": 
      "$map": 
        "input": "$friends",
        "in": "$$this._id"
      
    ,
    "friendsStatus": 
      "$ifNull": [  "$min": "$friends.status" , 0 ]
    
  
])

对于早期版本,最好实际使用$unwind,以确保您不会违反BSON Limit:

User.aggregate([
   "$lookup": 
    "from": Friend.collection.name,
    "localField": "friends",
    "foreignField": "_id",
    "as": "friends"
  ,
   "$unwind":  "path": "$friends", "preserveNullAndEmptyArrays": true  ,
   "$match": 
    "$or": [
       "friends.recipient": ObjectId("5afaab572c4ec049aeb0bcba") ,
       "friends": null 
    ]
  ,
   "$group": 
    "_id": "$_id",
    "firstName":  "$first": "$firstName" ,
    "friends":  "$push": "$friends._id" ,
    "friendsStatus": 
      "$min":  
        "$ifNull": ["$friends.status",0]
      
    
  
])

这里与最最佳形式存在“一个区别”,因为管道优化实际上并未将$match 条件“汇总”到@ 987654325@自己:


  "$lookup" : 
    "from" : "friends",
    "as" : "friends",
    "localField" : "friends",
    "foreignField" : "_id",
    "unwinding" : 
      "preserveNullAndEmptyArrays" : true
    
  
,

  "$match" :    // <-- outside will preserved array

由于preserveNullAndEmptyArrays 选项为true,因此“完全优化” 操作将条件实际应用于外部集合“之前” 结果不会被退回。

因此,unwinding 的唯一目的纯粹是为了避免 $lookup 结果中的通常目标“数组”导致父文档增长超出 BSON 限制。然后在此阶段“之后”应用$match 的附加条件。默认$unwind 不带选项假定false 用于保存,并添加matching 条件来代替执行此操作。这当然会导致没有外部匹配的文档被排除在外。

因为BSON Limit 并不是真正可取的,但也有将$filter 应用于$lookup 的结果数组:

User.aggregate([
   "$lookup": 
    "from": Friend.collection.name,
    "localField": "friends",
    "foreignField": "_id",
    "as": "friends"
  ,
   "$addFields": 
    "friends": 
      "$map": 
        "input": 
          "$filter": 
            "input": "$friends",
            "cond": 
              "$eq": [
                "$$this.recipient",
                ObjectId("5afaab572c4ec049aeb0bcba")
              ]
            
          
        ,
        "in": "$$this._id"
      
    ,
    "friendsStatus": 
      "$ifNull": [
         "$min": 
          "$map": 
            "input": 
              "$filter": 
                "input": "$friends",
                "cond": 
                   "$eq": [
                     "$$this.recipient",
                      ObjectId("5afaab572c4ec049aeb0bcba")
                   ]
                
              
            ,
            "in": "$$this.status"
          
        ,
        0
      ]
    
  
])

在任何一种情况下,我们基本上都将“附加条件”添加到联接中,不仅是在直接相关的字段上,而且还具有查询的 ObjectId 值对 "recipient" 的附加约束。

不太确定您对"friendsStatus" 的期望是什么,因为结果是一个数组并且可能不止一个(据我所知),因此只需在此处应用$min 以从数组中提取一个值无论哪种情况。

每种情况下的控制条件是$ifNull,它适用于"friends" 输出数组中没有任何内容可供提取的情况,然后您只需返回0 的结果即可。

所有输出都一样:


        "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
        "firstName" : "bruce",
        "friends" : [
                ObjectId("5afd1c42af18d985a06ac306")
        ],
        "friendsStatus" : 2


        "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
        "firstName" : "clerk",
        "friends" : [ ],
        "friendsStatus" : 0

【讨论】:

$lookup argument 'unwinding: preserveNullAndEmptyArrays: true ' must be a string, is type object 当我使用"unwinding" : "preserveNullAndEmptyArrays" : true @AshishChoudhary 您实际上并没有自己输入。您正在查看我正在显示的“解释”输出。代码和输出之间的区别应该很清楚。有关详细说明,请参阅Aggregate $lookup Total size of documents in matching pipeline exceeds maximum document size。

以上是关于将条件匹配的字段添加到嵌套数组的主要内容,如果未能解决你的问题,请参考以下文章

匹配 MongoDB 中嵌套子文档的嵌套数组

比较嵌套循环中的信息并在匹配时进行标记

MongoDB/Mongoose - 仅当某个字段是唯一的时才将对象添加到对象数组中

如何将嵌套字段添加到我的 BigQuery 表架构?

Elasticsearch - 将普通字段过滤器添加到嵌套字段聚合

Python - 将字段和标签添加到嵌套的 json 文件