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

时间:2018-05-17 06:50:27

标签: node.js mongodb mongoose mongodb-query aggregation-framework

我已关注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"登录,而此_idrecipient

friends匹配

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

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

[{
        "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
        "firstName" : "bruce",
        "friends" : [ ObjectId("5afd1c42af18d985a06ac306") ],
        "friendStatus": 2
},
{
        "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
        "firstName" : "clerk",
        "friends" : [],
        "friendStatus": 0
}]

提前致谢!!!

1 个答案:

答案 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]
      }
    }
  }}
])

此处最佳表单中存在“one difference”,因为管道优化实际上并未将$match条件“汇总”到$lookup本身:

{
  "$lookup" : {
    "from" : "friends",
    "as" : "friends",
    "localField" : "friends",
    "foreignField" : "_id",
    "unwinding" : {
      "preserveNullAndEmptyArrays" : true
    }
  }
},
{
  "$match" : {   // <-- outside will preserved array

由于preserveNullAndEmptyArrays选项为true,因此“完全优化”操作,其中条件实际将应用于外部集合“之前”< / em>返回的结果不会发生。

所以这里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"值的附加约束。 / p>

不确定你对"friendsStatus"的期望是什么,因为结果是一个数组,并且可能有多个(据我所知),因此只需在此处应用$min来提取一个在任何一种情况下都来自数组的值。

每种情况下的管理条件是$ifNull,适用于"friends"输出数组中没有任何内容要提取的情况,然后您只需返回0的结果就是这样。

所有输出相同的东西:

{
        "_id" : ObjectId("5afadfdf08a7aa6f1a27d986"),
        "firstName" : "bruce",
        "friends" : [
                ObjectId("5afd1c42af18d985a06ac306")
        ],
        "friendsStatus" : 2
}
{
        "_id" : ObjectId("5afbfe21daf4b13ddde07dbe"),
        "firstName" : "clerk",
        "friends" : [ ],
        "friendsStatus" : 0
}