> 文章列表 > MongoDB 聚合管道的字段投影($addFields,$set,$unset,$project)

MongoDB 聚合管道的字段投影($addFields,$set,$unset,$project)

MongoDB 聚合管道的字段投影($addFields,$set,$unset,$project)

上一篇我们介绍了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,则报了异常。