上一篇我们介绍了MongoDB 聚合管道的文档筛选及分组统计:
$match:文档过滤
$group:文档分组,并介绍了分组中的常用操作:$addToSet,$avg,$sum,$min,$max等。
如果需要进一步了解可以参考:
MongoDB 聚合管道的文档筛选及分组统计https://blog.csdn.net/m1729339749/article/details/130034658这篇我们主要介绍使用聚合管道实现字段映射:
一、准备工作
初始化零食数据
db.goods.insertMany([
{ "_id": 1, name: "薯片", size: "S", quantity: 10, price: 8, expirationTime: ISODate( "2023-08-08T00:00:00Z" ) },
{ "_id": 2, name: "薯片", size: "L", quantity: 8, price: 12, expirationTime: ISODate( "2023-08-08T00:00:00Z" ) },
{ "_id": 3, name: "牛肉干", size: "L", quantity: 5, price: 30, expirationTime: ISODate( "2023-10-10T00:00:00Z" ) },
{ "_id": 4, name: "可口可乐", size: "S", quantity: 10, price: 3, expirationTime: ISODate( "2025-01-06T00:00:00Z" ) },
{ "_id": 5, name: "可口可乐", size: "L", quantity: 6, price: 10, expirationTime: ISODate( "2025-01-06T00:00:00Z" ) },
{ "_id": 6, name: "旺仔牛奶", size: "L", quantity: 10, price: 5, expirationTime: ISODate( "2023-08-10T00:00:00Z" )}
])
二、新增字段($addFields)
语法:{ $addFields: { <newField>: <expression>, ... } }
添加字段,从4.2开始后也可以使用$set增加字段
例子:在聚合数据中增加 comment 字段
db.goods.aggregate([
{
$addFields: { "comment": "我是新增加的" }
}
])
此操作等效于:
db.goods.aggregate([
{
$set: { "comment": "我是新增加的" }
}
])
聚合查询的结果如下:
{ "_id" : 1, "name" : "薯片", "size" : "S", "quantity" : 10, "price" : 8, "expirationTime" : ISODate("2023-08-08T00:00:00Z"), "comment" : "我是新增加的" }
{ "_id" : 2, "name" : "薯片", "size" : "L", "quantity" : 8, "price" : 12, "expirationTime" : ISODate("2023-08-08T00:00:00Z"), "comment" : "我是新增加的" }
{ "_id" : 3, "name" : "牛肉干", "size" : "L", "quantity" : 5, "price" : 30, "expirationTime" : ISODate("2023-10-10T00:00:00Z"), "comment" : "我是新增加的" }
{ "_id" : 4, "name" : "可口可乐", "size" : "S", "quantity" : 10, "price" : 3, "expirationTime" : ISODate("2025-01-06T00:00:00Z"), "comment" : "我是新增加的" }
{ "_id" : 5, "name" : "可口可乐", "size" : "L", "quantity" : 6, "price" : 10, "expirationTime" : ISODate("2025-01-06T00:00:00Z"), "comment" : "我是新增加的" }
{ "_id" : 6, "name" : "旺仔牛奶", "size" : "L", "quantity" : 10, "price" : 5, "expirationTime" : ISODate("2023-08-10T00:00:00Z"), "comment" : "我是新增加的" }
三、移除字段($unset)
语法:{ $unset: "<field>" }
或 { $unset: [ "<field1>", "<field2>", ... ] }
移除字段,移除一个字段或者移除多个字段
例子:在聚合数据中移除 expirationTime字段
db.goods.aggregate([
{
"$unset": "expirationTime"
}
])
聚合查询的结果如下:
{ "_id" : 1, "name" : "薯片", "size" : "S", "quantity" : 10, "price" : 8 }
{ "_id" : 2, "name" : "薯片", "size" : "L", "quantity" : 8, "price" : 12 }
{ "_id" : 3, "name" : "牛肉干", "size" : "L", "quantity" : 5, "price" : 30 }
{ "_id" : 4, "name" : "可口可乐", "size" : "S", "quantity" : 10, "price" : 3 }
{ "_id" : 5, "name" : "可口可乐", "size" : "L", "quantity" : 6, "price" : 10 }
{ "_id" : 6, "name" : "旺仔牛奶", "size" : "L", "quantity" : 10, "price" : 5 }
四、字段投影($project)
语法:{ $project: { <specification(s)> } }
对文档中的字段进行投影操作,例如:保留哪些字段、移除哪些字段、添加新的字段、重置字段的值等。
specifications的定义如下:
<field>: 1 or true:代表的是保留某个字段
<field>: 0 or false:代表的是移除某个字段
<field>: <expression>:代表的是添加新的字段或者重置字段的值
例子:移除商品的生产日期、编号,添加商品的总价值,并把size的值替换成小包装、大包装
db.goods.aggregate([
{
$project: {
"name": 1,
"price": 1,
"quantity": 1,
"size": {
$cond: {
if: { $eq: [ "S", "$size"] },
then: "小包装",
else: "大包装"
}
},
"totalWorth": { $multiply: [ "$quantity", "$price" ] }
}
}
])
解释一下给出的聚合查询语句:
(1) 保留name、price、quantity三个字段;
(2) 重置size的值,如果字段size的值与"S"相等,返回“小包装”作为值,否则返回“大包装”作为值;里面的表达式运算符我们先做简单的介绍,后面会出一篇文章单独介绍;
(3) 新增总价值字段totalWorth,将数量与价格相乘的结果作为新增字段的值;里面的表达式运算符我们先做简单的介绍,后面会出一篇文章单独介绍
下面我们看一下聚合查询的结果:
{ "_id" : 1, "name" : "薯片", "quantity" : 10, "price" : 8, "size" : "小包装", "totalWorth" : 80 }
{ "_id" : 2, "name" : "薯片", "quantity" : 8, "price" : 12, "size" : "大包装", "totalWorth" : 96 }
{ "_id" : 3, "name" : "牛肉干", "quantity" : 5, "price" : 30, "size" : "大包装", "totalWorth" : 150 }
{ "_id" : 4, "name" : "可口可乐", "quantity" : 10, "price" : 3, "size" : "小包装", "totalWorth" : 30 }
{ "_id" : 5, "name" : "可口可乐", "quantity" : 6, "price" : 10, "size" : "大包装", "totalWorth" : 60 }
{ "_id" : 6, "name" : "旺仔牛奶", "quantity" : 10, "price" : 5, "size" : "大包装", "totalWorth" : 50 }
可以看到里面的编号没有去除掉,原因是_id作为主键会自动保留,需要我们使用_id: 0 给强制去除掉:
db.goods.aggregate([
{
$project: {
"_id": 0,
"name": 1,
"price": 1,
"quantity": 1,
"size": {
$cond: {
if: { $eq: [ "S", "$size"] },
then: "小包装",
else: "大包装"
}
},
"totalWorth": { $multiply: [ "$quantity", "$price" ] }
}
}
])
再次执行聚合查询的结果如下:
{ "name" : "薯片", "quantity" : 10, "price" : 8, "size" : "小包装", "totalWorth" : 80 }
{ "name" : "薯片", "quantity" : 8, "price" : 12, "size" : "大包装", "totalWorth" : 96 }
{ "name" : "牛肉干", "quantity" : 5, "price" : 30, "size" : "大包装", "totalWorth" : 150 }
{ "name" : "可口可乐", "quantity" : 10, "price" : 3, "size" : "小包装", "totalWorth" : 30 }
{ "name" : "可口可乐", "quantity" : 6, "price" : 10, "size" : "大包装", "totalWorth" : 60 }
{ "name" : "旺仔牛奶", "quantity" : 10, "price" : 5, "size" : "大包装", "totalWorth" : 50 }
扩展:如何区分inclusion projection、exclusion projection
默认情况下是inclusion projection映射;
如果定义中出现的第一个非_id字段定义的映射为<field>: 0,则是exclusion projection映射;
如果定义中出现的第一个非_id字段定义的映射为<field>: 1,则是inclusion projection映射;
例子1:
db.goods.aggregate([
{
$project: {
"_id": 0,
"name": 1
}
}
])
如上面的例子,第一个非_id的字段定义的映射是"name": 1,则判断其是inclusion projection映射
例子2:
db.goods.aggregate([
{
$project: {
"name": 0
}
}
])
如上面的例子,第一个非_id的字段定义的映射是"name": 0,则判断其是exclusion projection映射
扩展:inclusion projection中除_id:0外只能包含<field>: 1
exclusion projection中除_id:1外只能包含<field>:0
例子1:
db.goods.aggregate([
{
$project: {
"name": 1,
"size": 0
}
}
])
执行上面的聚合查询会报错:
{
"ok" : 0,
"errmsg" : "Invalid $project :: caused by :: Cannot do exclusion on field size in inclusion projection",
"code" : 31254,
"codeName" : "Location31254"
}
分析原因:
第一个非_id的字段定义的映射是"name": 1,则判断其是inclusion projection映射,inclusion projection映射中除_id:0外只能包含<field>: 1,现在包含了"size": 0,则报了异常。
例子2:
db.goods.aggregate([
{
$project: {
"size": 0,
"name": 1
}
}
])
执行上面的聚合查询会报错:
{
"ok" : 0,
"errmsg" : "Invalid $project :: caused by :: Cannot do inclusion on field name in exclusion projection",
"code" : 31253,
"codeName" : "Location31253"
}
分析原因:
第一个非_id的字段定义的映射是"size": 0,则判断其是exclusion projection映射,exclusion projection映射中除_id:1外只能包含<field>: 0,现在包含了"name": 1,则报了异常。