先看案例。
订单orders集合的情况
db.orders.insertMany( [
{ "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 },
{ "_id" : 2, "item" : "cookies", "price" : 10, "ordered" : 60 }
] )
仓库warehouses集合的情况
db.warehouses.insertMany( [
{ "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 },
{ "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 },
{ "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 }
] )
上面这个是官方案例的情况,从订单表入手聚合仓库表信息,形成每个item的订单数量、仓库位置信息对象。
具体看官方链接就可以了。$lookup (aggregation) — MongoDB Manual
下面是一些解释说明
db.orders.aggregate( [ //从orders表入手
{
$lookup:
{
from: "warehouses",//聚合查询warehouse表
let: { order_item: "$item"}, //用$$order_item指代order表的.item字段。$$可以理解成lookup中的变量前缀,$表示当前层级
pipeline: [
{ $match: //在warehouse中匹配
{ $expr: //表达式
{ $eq: [ "$stock_item", "$$order_item" ] }, //eq即等于,注意这里的变量名,$stock_item是warehouse表的,$$order_item是来自order表lookup的变量。
}
},
{ $project: { stock_item: 0, _id: 0 } }, //各种对warehouse表查询的限定都放在这里
{ $limit:10},
{ $skip:0},
],
as: "stockdata" //输出字段名,就是会把warehouse中查到的信息放到结果对象的.stockdata中。
}
}
] )
简单情况
如果我们不要对嵌套查询的表格做限定,只是单纯的吧信息集中过来,那么可以简单得多。
比如article表文档格式如{_id:..,title:...,authorId'...}
,而user表的文档格式如{_id:...,name:xxx,password:'xxxx'}
,那么我们可以用下面的pipline管道完成聚合(Golang实现)。
articleidObj, _ := primitive.ObjectIDFromHex("xxx")
useridObj, _ := primitive.ObjectIDFromHex("xxx")
pipline := []bson.M{
{
"$match":bson.M{ //找到文章
"_id": articleidObj,
},
},
{
"$lookup": bson.M{
"from": "user",
"localField": "authorId", //article文档中的字段
"foreignField": "_id", //user文档中的字段
"as": "author", //user表查出结果放到这个字段
},
},
{
"$project": bson.M{
"title": 1, //注意这里!authorid字段将不出现在结果里
"author":bson.M{ //注意这里!不是用authorid,而是用as的author
"name": 1, //注意这里!结果里不会出现password字段
},
},
},
{
"$sort": bson.M{"Ts": -1}, //文章排序
},
{
"$skip": Skip,
},
{
"$limit": Limit,
},
}
opts := options.Aggregate().SetMaxTime(1 * time.Second)
cur, err := dbc.Aggregate(ctx, pipline, opts)
var vli []bson.M
if err != nil {
return uds.RespErr(err.Error()), nil
}
for cur.Next(context.TODO()) {
var v bson.M
err := cur.Decode(&v)
if err != nil {
return uds.RespErr(err.Error()), nil
}
vli = append(vli, v)
}
注意这里的$project,$limit...都是针对外层数据集article的,不是针对内层限定的,就是最终最多返回limit个article,而article.author里面有多少个并不影响(当然这里_id是唯一的,所以只会返回一个)
什么时候使用let
假如我们处理的不是文章的作者,而是文章的读者数据,怎么办?
article.readers肯定是个列表,可以是[userid1,userid2,userid3]
这种,也可以是[{uid:xxx,time:xxx},[{uid:yyy,time:yyy}]
这种。
对于article.readers=[userid1,userid2,userid3]
这种情况,要把读者user姓名写进article查询结果,格式大致如下:
pipline := []bson.M{
{
"$match": bson.M{
"_id": articleidObj,
},
},
{
"$lookup": bson.M{
"from": "user",
"as": "readers",
"let": bson.M{"readersids": "$readers"}, //注意这里!
"pipeline": []bson.M{
{
"$expr": bson.M{
"$in": bson.A{"$_id", "$$readersids"}, //注意这里的$in
},
},
{
"$skip": 0,
},
{
"$limit": 2,
},
{
"$project": bson.M{
"name": 1, //注意这里!
},,
},
},
},
},
}
列表是对象的情况
对于article.readers=[{uid:xxx,time:xxx},[{uid:yyy,time:yyy}]
这种情况,要把读者user姓名写进article查询结果,格式大致如下:
pipline := []bson.M{
{
"$match": bson.M{
"_id": articleidObj,
},
},
{
"$lookup": bson.M{
"from": "user",
"as": "readers",
"let": bson.M{"readersid": "$readers.uid"}, //注意这里!
"pipeline": []bson.M{
{
"$expr": bson.M{
"$eq": bson.A{"$_id", "$$readersid"}, //注意这里的$eq
},
},
{
"$skip": 0,
},
{
"$limit": 2,
},
{
"$project": bson.M{
"name": 1,
},
},
},
},
},
}
简要汇总
对于查A表时候同时用A.arr数组字段嵌套查B表,结果放入A.as字段,其中lookup内pipeline变量的规则是arr.key表示数组对象的子字段。在lookup中注意$是B表字段,$$是lookup中定义的A表字段。
lookup中的localfield不与pipeline字段一起用,pipeline只能和let一起用。
最后查询结果是来自A表的,知识其中的A.as字段来自B表,所以最外层的$project中是可以对A.as内的字段进行设定的,很多时候这样可以省事很多。