MongoDB聚合技巧|当有对象数组时lookup联表查询

449 阅读1分钟

需求

电商购物场景下会有一个很常见的需求:

  • 产品表(products)保存产品的详细参数。
  • 订单表(orders)保存用户的购买记录,但是在订单表不保存产品详情,而是通过一个 productId 字段与产品表关联。

就像是这样:

//产品表
[
  {_id:1, name:'Macbook Pro M1', desc:'苹果最新的M1笔记本', price:1200 },
  {_id:2, name:'联想拯救者9600', desc:'专业的游戏本本', price:1500 },
  {_id:3, name:'戴尔xps', desc:'轻薄商务本', price:20 },
  {_id:4, name:'华为笔记本', desc:'鸿蒙系统', price:100 },
  {_id:5, name:'红米笔记本', desc:'性价比最优', price:80 }
]

//订单表
[
  {
    name:'张三', sex:'男',
    products:[
      { productId:1, count:1 },//一台苹果笔记本
      { productId:2, count:1 } //一台联想笔记本
    ]
  },{
    name:'赵梅', sex:'女',
    products:[
      { productId:3, count:2 },//两台戴尔笔记本
      { productId:4, count:1 } //一台华为笔记本
    ]
  }
]

现在要让订单和产品详情一起显示,像这样:

{
    _id:编号,
    name:姓名,
    sex:性别,
    products:[
        {
            productId:产品编号,
            count:产品数量,
            product://产品详情
            {
                _id:产品编号
                name:产品名称
                desc:产品描述
                price:产品价格
            }
        },
        {
            ...
        }
    ]
}

问题

那么在 MongoDB 里面你会如何来做呢?

你可能立刻会想到使用 lookup ,比如:

db.orders.aggregate([
  {
    $lookup: {
      from: 'products',
      localField: 'products.productId',
      foreignField: '_id',
      as: 'products.product'
    }
  }
])

但是,这样查询出来并不是你所想要的结果。他的结果是这样的。

11111.png

也就是说原来的products内容被lookup出来的内容覆盖了。所以这个简单的代码是行不通的。我们需要转换一下思路。

仔细观察一下,我们会发现这个 lookup 的产品详情的顺序是与我们 products 中的顺序一致的。这样的话我们可以通过遍历products,然后把对应位置上的产品详情合并进去就行了。

解决方案

思路确定了,实现就简单了。以下是最终的代码,总共分三个阶段聚合:

  1. 通过lookup聚合创建一个新的 productDetail 的字段。
  2. 使用addFields覆盖原来的products,对products的每个元素遍历,并从productDetail中合并对应位置的信息。
  3. 删除productDetail字段。
db.orders.aggregate([
  {
    $lookup: {
      from: 'products',
      localField: 'products.productId',
      foreignField: '_id',
      as: 'productDetail'
    }
  },
  {
    $addFields: {
      products: {
        $map:{
          input:"$products",
          as:'product',
          in:{
            $mergeObjects:[
              '$$product',
              {product:{$arrayElemAt:['$productDetail',{$indexOfArray:['$products','$$product']}]}}
            ]
          }
        }
      }
    }
  },
  {
    $unset:["productDetail"]
  }
])

执行结果如下:

11111.png