目前为止,我们已经介绍了一部分聚合管道中的管道参数:
$match:文档过滤
$group:文档分组,并介绍了分组中的常用操作:$addToSet,$avg,$sum,$min,$max等。
$addFields:添加字段,等效于$set
$unset:移除字段
$project:字段投影
$sort:文档排序
$skip:跳过N条文档
$limit:获取N条文档
$sample:随机抽取N条文档
$unwind:分解文档
如果需要进一步了解可以参考:
MongoDB 聚合管道的文档筛选及分组统计https://blog.csdn.net/m1729339749/article/details/130034658MongoDB 聚合管道的字段投影https://blog.csdn.net/m1729339749/article/details/130055110MongoDB 聚合管道的文档操作https://blog.csdn.net/m1729339749/article/details/130066663本篇我们介绍聚合管道的集合关联及合并操作:
一、准备工作
初始化零食数据
db.snacks.insertMany([{ "_id": 1, name: "薯片" },{ "_id": 2, name: "牛肉干" },{ "_id": 3, name: "可口可乐" },{ "_id": 4, name: "旺仔牛奶" }
])
初始化零食型号及储量
db.snacksType.insertMany([{ "_id": 1, size: "S", quantity: 10, price: 8, expirationTime: ISODate( "2023-08-08T00:00:00Z" ), "snacks_id": 1 },{ "_id": 2, size: "L", quantity: 8, price: 12, expirationTime: ISODate( "2023-08-08T00:00:00Z" ), "snacks_id": 1 },{ "_id": 3, size: "L", quantity: 5, price: 30, expirationTime: ISODate( "2023-10-10T00:00:00Z" ), "snacks_id": 2 },{ "_id": 4, size: "S", quantity: 10, price: 3, expirationTime: ISODate( "2025-01-06T00:00:00Z" ), "snacks_id": 3 },{ "_id": 5, size: "L", quantity: 6, price: 10, expirationTime: ISODate( "2025-01-06T00:00:00Z" ), "snacks_id": 3 },{ "_id": 6, size: "L", quantity: 10, price: 5, expirationTime: ISODate( "2023-08-10T00:00:00Z" ), "snacks_id": 4}
])
二、使用字段关联查询($lookup)
语法:
{$lookup:{from: <collection to join>,localField: <field from the input documents>,foreignField: <field from the documents of the "from" collection>,as: <output array field>}
}
from:指的是关联的集合
localField:指的是当前管道中的字段
foreignField:指的是关联的集合中的字段
as:输出的数组字段;此字段将作为当前管道中的字段,字段的值来源于关联的集合中。
例子:在零食文档中关联上零食对应的所有型号信息
db.snacks.aggregate([{$lookup: {from: "snacksType",localField: "_id",foreignField: "snacks_id",as: "types"}}
])
我们对上面的聚合查询语法进行解释:
使用snacks集合中的字段_id与snacksType集合中的字段snacks_id进行关联查询snacksType集合中的文档,并将查询的结果作为当前管道中types字段的值。
聚合查询的结果如下:
{"_id" : 1,"name" : "薯片","types" : [{"_id" : 1,"size" : "S","quantity" : 10,"price" : 8,"expirationTime" : ISODate("2023-08-08T00:00:00Z"),"snacks_id" : 1},{"_id" : 2,"size" : "L","quantity" : 8,"price" : 12,"expirationTime" : ISODate("2023-08-08T00:00:00Z"),"snacks_id" : 1}]
}
{"_id" : 2,"name" : "牛肉干","types" : [{"_id" : 3,"size" : "L","quantity" : 5,"price" : 30,"expirationTime" : ISODate("2023-10-10T00:00:00Z"),"snacks_id" : 2}]
}
{"_id" : 3,"name" : "可口可乐","types" : [{"_id" : 4,"size" : "S","quantity" : 10,"price" : 3,"expirationTime" : ISODate("2025-01-06T00:00:00Z"),"snacks_id" : 3},{"_id" : 5,"size" : "L","quantity" : 6,"price" : 10,"expirationTime" : ISODate("2025-01-06T00:00:00Z"),"snacks_id" : 3}]
}
{"_id" : 4,"name" : "旺仔牛奶","types" : [{"_id" : 6,"size" : "L","quantity" : 10,"price" : 5,"expirationTime" : ISODate("2023-08-10T00:00:00Z"),"snacks_id" : 4}]
}
可以看出每个零食文档中都增加了一个数组字段types,里面包含了零食对应的型号
三、使用管道关联查询($lookup)
语法:
{$lookup:{from: <joined collection>,let: { <var_1>: <expression>, …, <var_n>: <expression> },pipeline: [ <pipeline to run on joined collection> ],as: <output array field>}
}
from:值的是关联的集合
let:可选,定义变量,这些变量可以在参数pipeline中使用
pipeline:指的是运行在关联的集合上的管道
as:输出的数组字段;此字段将作为当前管道中的字段,字段的值来源于关联的集合执行管道操作后输出的文档。
例子:在零食文档中关联上零食对应的型号、价格、储量、总价值信息
第一步:我们使用管道查询型号集合中的型号、总价值信息
db.snacksType.aggregate([{$project: {"_id": 0,"size": 1,"totalValue": { $multiply: [ "$price", "$quantity" ] }}}
])
执行的结果如下:
{ "size" : "S", "totalValue" : 80 }
{ "size" : "L", "totalValue" : 96 }
{ "size" : "L", "totalValue" : 150 }
{ "size" : "S", "totalValue" : 30 }
{ "size" : "L", "totalValue" : 60 }
{ "size" : "L", "totalValue" : 50 }
第二步:根据零食关联型号信息
db.snacks.aggregate([{$lookup: {from: "snacksType",let: { "snackId": "$_id" },pipeline: [{ $match: { $expr: { $eq: [ "$snacks_id", "$$snackId" ] } } },{$project: {"_id": 0,"size": 1,"totalValue": { $multiply: [ "$price", "$quantity" ] }}}],as: "types"}}
])
我们对上面的聚合查询语法进行解释:
定义了snackId变量用于存储零食的编号(_id);在集合snacksType对应的管道上使用字段snacks_id对文档进行过滤,然后再进行字段投影。
定义的变量在使用时需要使用 $$ + 变量名;如果使用变量时,需要使用$expr运算符(在$expr运算符内部使用变量)
执行的结果如下:
{"_id" : 1,"name" : "薯片","types" : [{"size" : "S","totalValue" : 80},{"size" : "L","totalValue" : 96}]
}
{"_id" : 2,"name" : "牛肉干","types" : [{"size" : "L","totalValue" : 150}]
}
{"_id" : 3,"name" : "可口可乐","types" : [{"size" : "S","totalValue" : 30},{"size" : "L","totalValue" : 60}]
}
{"_id" : 4,"name" : "旺仔牛奶","types" : [{"size" : "L","totalValue" : 50}]
}
四、合并集合管道
语法:{ $unionWith: { coll: "<collection>", pipeline: [ <stage1>, ... ] } }
合并两个集合的管道
<collection>代表的是集合或者视图
[ <stage1>, ... ] 代表的是对<collection>进行处理的多个阶段组成的管道
例子:合并零食与型号集合中的文档
db.snacks.aggregate([{$project: { "name": 1, "_id": 0 }},{$unionWith: {coll: "snacksType",pipeline: [{ $project: { "size": "1", "_id": 0 } } ]}}
])
我们对聚合查询进行一下解释:
(1) 使用$project 对snacks集合中的文档进行投影,投影后只保留了name字段
(2) 使用$project 对snacksType集合中的文档进行投影,投影后只保留了size字段
(3) 使用$unionWith 对两个集合的管道进行合并
执行的结果如下:
{ "name" : "薯片" }
{ "name" : "牛肉干" }
{ "name" : "可口可乐" }
{ "name" : "旺仔牛奶" }
{ "size" : "S" }
{ "size" : "L" }
{ "size" : "L" }
{ "size" : "S" }
{ "size" : "L" }
{ "size" : "L" }