MongoDB $lookup 在 2 级嵌套文档上不使用 $unwind

Posted

技术标签:

【中文标题】MongoDB $lookup 在 2 级嵌套文档上不使用 $unwind【英文标题】:MongoDB $lookup on 2 level nested document without using $unwind 【发布时间】:2020-11-26 21:19:48 【问题描述】:

我有以下文件

loanRequest(只写我想投影的键)


  "_id": "5f2bf26783f65d33026ea592",
  "lendingpartner":  
     /* some keys here */
  ,
  "loans": [
    
      "loanid": 43809,
      "loanamount": 761256,
      "jewels": [
        "5f2bf26783f65d33026ea593",
        "5f2bf26783f65d33026ea594"
        "5f2bf26783f65d33026ea595"
      ],
    
  ]

质押珠宝


  "_id": "5f2bf26783f65d33026ea593",
  "netweight": 8.52,
  "purity": 19,


我想要实现的是


  "_id": "5f2bf2b583f65d33026ea603",
  "lendingpartner":  
     /* some keys here */
  ,
  "loans": [
    
      "loanid": 40010,
      "loanamount": 100000,
      "jewels": [
        
          "_id": "5f2bf26783f65d33026ea593",
          "netweight": 8.52,
          "purity": 19,
        ,
        
          "_id": "5f2bf26783f65d33026ea594",
          "netweight": 5.2,
          "purity": 40,
        ,
        
          "_id": "5f2bf26783f65d33026ea595",
          "netweight": 4.52,
          "purity": 39,
        
      ]
    
  ]

由于我希望将珠宝详细信息填充到每笔贷款的珠宝数组中,$unwind 不会帮助我。 (我试过用它做实验)

我以为我可以在贷款数组上运行$map,然后为贷款的每个宝石运行$lookup(双地图?),但无法提出可行的解决方案。 无论如何,这似乎不是正确的方法。


这是我能想到的最好的结果(与我想要的结果相去甚远)。我正在使用地图有选择地从贷款对象中选择键。

const loanrequests = await db.collection('loanrequest').aggregate([
   $match:  requester: ObjectID(user.id)  ,
  
    $project: 
      lendingpartner: 
        name: 1,
        branchname: '$branch.branchname',
      ,
      loans: 
        $map: 
          input: '$loans',
          as: 'loan',
          in: 
            loanid: '$$loan.loanid',
            loanamount: '$$loan.amount',
            jewels: '$$loan.jewels',
          ,
        ,
      ,
    ,
  ,
  /*
  * I experimented with unwind here. Tried adding 
  *  $unwind: '$loans' ,
  *  $unwind: '$loans.jewels' 
  * but it does not give me the result I need (as already said before)
  */
]).toArray();

我想,我需要在投影之前执行$lookup,但由于文档的 2 级嵌套结构(首先是 loans 数组,然后是 @987654331 @)

我今天开始使用 mongodb 聚合器,在寻找答案时,我偶然发现了类似的 Question,但它似乎更复杂,因此我更难理解。

谢谢!

【问题讨论】:

【参考方案1】:

如果您没有尝试使用聚合来实现其他目标,则可以在 mongoose 中使用 .populate。

LoanReqests
  .find(
    requester: user.id,
    name: 1, branch: 1, loans: 1 // Projection
  )
  .populate('loans.jewels');

如果您必须使用聚合来执行示例中没有的操作,那么 $unwind 确实是您最好的选择,但是在 $lookup 之后进行 $grouping 以获得您想要的输出。如果这对您不起作用,您能否详细说明 $unwind 的问题是什么?我猜这与您的问题中未列出的字段有关。

https://mongoplayground.net/p/O5pxWNy99J4

db.loanRequests.aggregate([
  
    $project: 
      name: 1,
      loans: 1,
      branch: "$branch.name"
    
  ,
  
    $unwind: "$loans"
  ,
  
    $lookup: 
      localField: "loans.jewels",
      foreignField: "_id",
      from: "jewels",
      as: "loans.jewels"
    
  ,
  
    $group: 
      _id: "$_id",
      name: 
        $first: "$name"
      ,
      branch: 
        $first: "$branch"
      ,
      loans: 
        $push: "$loans"
      
    
  
])

【讨论】:

你是对的,$unwind 后跟 $group 会导致所需的输出。以前不知道$group,我对如何“解散”(在运行$unwind 之后)一无所知。我尝试建立您的解决方案,并获得了预期的结果。【参考方案2】:

正如@GitGitBoom 在上一个答案中提到的,$unwind 后跟$group 应该是这种方法。

当然,在分组之前(我认为它是“展开”运行 unwind 的结果),我需要运行 $lookup 以填充 loans.jewels

这是基于上一个答案构建的整个解决方案。

const loanRequests = await db.collection('loanRequest').aggregate([
   $match:  requester: ObjectID(user.id)  ,
  
    $project: 
      lender: '$lendingpartner.name',
      branch: '$lendingpartner.branch.branchname',
      loans: 1,
    ,
  ,
   $unwind: '$loans' ,
  
    $lookup: 
      localField: 'loans.jewels',
      from: 'pledgedJewel',
      foreignField: '_id',
      as: 'loans.jewels',
    ,
  ,
  
    $group: 
      _id: '$_id',
      branch:  $first: '$branch' ,
      lender:  $first: '$lender' ,
      loans:  $push: '$loans' ,
    ,
  ,
  
    $project: 
      _id: 1,
      branch: 1,
      lender: 1,
      loans: 1,
    ,
  ,
]).toArray();

类型不匹配的问题

另一个问题是,我的$lookup 由于类型不匹配而无法正常工作。在我运行聚合的 loanRequest 集合中,loans.jewels 中的 id 是 string 类型,而承诺珠宝中的外部字段 _idObjectId

这可以通过使用$toObjectId$toString 来解决(仅在mongodb 版本>= 4.0 中支持)

 $project:  jewelObjId:  $toObjectId: '$loans.jewels'   ,   // for mongodb >= 4.0

  $lookup: 
    localField: 'jewelObjId',  // for mongodb >= 4.0
    from: 'pledgedjewel',
    foreignField: '_id',
    as: 'loans.jewels',
  ,
,

但是,我在较低版本的 mongodb 上运行,因此这些聚合对我不起作用。解决这个问题的唯一方法是将loans.jewels 的类型更改为ObjectId,而不是像我那样保持string

关于类型不匹配的更多信息

Need a workaround for lookup of a string to objectID foreignField

Mongodb Join on _id field from String to ObjectId

【讨论】:

以上是关于MongoDB $lookup 在 2 级嵌套文档上不使用 $unwind的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB 聚合 - 我如何“$lookup”嵌套文档“_id”?

MongoDb:使用 $lookup 查找深度嵌套的对象

MongoDb:使用 $lookup 查找深度嵌套的对象

mongodb 中的 $lookup 嵌套数组

具有 3 个级别的 MongoDB 嵌套查找并将新值附加到结果文档

$lookup 嵌套文档