需求
电商购物场景下会有一个很常见的需求:
- 产品表(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'
}
}
])
但是,这样查询出来并不是你所想要的结果。他的结果是这样的。
也就是说原来的products内容被lookup出来的内容覆盖了。所以这个简单的代码是行不通的。我们需要转换一下思路。
仔细观察一下,我们会发现这个 lookup 的产品详情的顺序是与我们 products 中的顺序一致的。这样的话我们可以通过遍历products,然后把对应位置上的产品详情合并进去就行了。
解决方案
思路确定了,实现就简单了。以下是最终的代码,总共分三个阶段聚合:
- 通过lookup聚合创建一个新的 productDetail 的字段。
- 使用addFields覆盖原来的products,对products的每个元素遍历,并从productDetail中合并对应位置的信息。
- 删除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"]
}
])
执行结果如下: