熟悉Linux系统的小伙伴应该知道Linux中有管道的说法,可以用来方便地处理数据。
MongoDB 2.2版本也引入了新的数据聚合框架,一个文档可以经过多个节点组成的管道,每个节点都有自己特殊的功能,比如文档分组、文档过滤等。每一个节点都会接收一连串的文档,对这些文档做一些类型转换,然后将转换后的文档传递给下一个节点,最后一个节点则会将结果返回给客户端。本文先来看几个基本的管道操作符。
$match
$match 可以用来对文档进行筛选。筛选完成之后,我们就可以在筛选获得到的文档子集上做数据聚合操作了。我们之前介绍的查询的操作符在 $match 中都可以使用,比如获取集合中所有 author为"杜甫"的文档,如下:
db.agg_1.aggregate({$match:{author:"杜甫"}})
我们在实际使用时最好将 $match 放在管道的前面,这样可以减少后边管道的工作量。同时,我们在投射和分组之前执行 $match 还可以用索引。
$project
基本用法
$project可以用来提取想要的字段,如下:
db.agg_1.aggregate({$project:{title:1,_id:0}})
1表示要该字段,0表示不要该字段。也可以对返回的字段进行重命名,比如将 title 改为 articleTitle ,如下:
db.agg_1.aggregate({$project:{"articleTitle":"$title"}})
不过这里有一个问题需要注意:如果原字段上有索引,重命名之后的字段上就没有索引了,因此最好在重命名之前使用索引。
数学表达式
数学表达式可以用来对一组数值进行加减乘除取模,比如我的数据结构如下:
{
"_id":ObjectId("61a71128fcabfe7d198aac94"),
"orderAddressL":"shenzhen",
"prodMoney":45,
"freight":13,
"discounts":3,
"orderDate":ISODate("2021-12-01T06:07:36.517Z"),
"prods":[
"可乐",
"奶茶"
]
}
订单的总费用为商品费用加上运费,查询如下:
db.agg_2.aggregate({$project:{totalMoney:{$add:["$prodMoney","$freight"]}}})
实际付款的费用是总费用减去折扣,如下:
db.agg_2.aggregate({$project:{totalPay:{$subtract:[{$add:["$prodMoney","$freight"]},"$discounts"]}}})
再来三个无厘头运算,比如计算 prodMoney 和 freight 和 discounts 的乘积,如下:
db.agg_2.aggregate({$project:{test1:{$multiply:["$prodMoney","$freight","$discounts"]}}})
再比如求 prodMoney 和 freight 的商,如下:
db.agg_2.aggregate({$project:{test2:{$divide:["$prodMoney","$freight"]}}})
再比如用 freight 对 prodMoney 取模,如下:
db.agg_2.aggregate({$project:{test3:{$mod:["$freight","$prodMoney"]}}})
加法和乘法都可以接收多个参数,其余的都接收两个参数。
日期表达式
日期表达式可以从一个日期类型中提取出年、月、日、星期、时、分、秒等信息,如下:
db.agg_2.aggregate({$project:{"年份":{$year:"$orderDate"},
"月份":{$month:"$orderDate"},"一年中第几周":{$week:"$orderDate"},
"日期":{$dayOfMonth:"$orderDate"},"星期":{$dayOfWeek:"$orderDate"},
"一年中第几天":{$dayOfYear:"$orderDate"},"时":{$hour:"$orderDate"},
"分":{$minute:"$orderDate"},"秒":{$second:"$orderDate"},
"毫秒":{$millisecond:"$orderDate"},
"自定义格式化时间":{$dateToString:{format:"%Y年%m月%d日 %H:%M:%S",date:"$orderDate"}}}})
执行结果如下:
{
"_id":ObjectId("61a71128fcabfe7d198aac94"),
"年份":2021,
"月份":12,
"一年中第几周":48,
"日期":1,
"星期":4,
"一年中第几天":335,
"时":6,
"分":7,
"秒":36,
"毫秒":517,
"自定义格式化时间":"2021年12月01日 06:07:36"
}
$dayOfWeek 返回的是星期,1表示星期天,7表示星期六。$week 表示本周是本年的第几周,从0开始计。$dateToString 是MongoDB 3.0+中的功能,格式化的字符还有以下几种:
字符 | 含义 | 取值范围 |
%Y | Year (4 digits, zero padded) | 0000-9999 |
%m | Month (2 digits, zero padded) | 01-12 |
%d | Day of Month (2 digits, zero padded) | 01-31 |
%H | Hour (2 digits, zero padded, 24-hour clock) | 00-23 |
%M | Minute (2 digits, zero padded) | 00-59 |
%S | Second (2 digits, zero padded) | 00-59 |
%L | Millisecond (3 digits, zero padded) | 000-999 |
%j | Day of Year (3 digits, zero padded) | 001-366 |
%w | Day of Week (1-Sunday,7-Saturday) | 1-7 |
%U | Week of Year (2 digits, zero padded) | 00-53 |
字符串表达式
字符串表达式中有字符串的截取、拼接、转大写、转小写等操作,比如我截取 orderAddressL 前两个字符返回,如下:
db.agg_2.aggregate({$project:{addr:{$substr:["$orderAddressL",0,2]}}})
再比如我将 orderAddressL 和 orderDate 拼接后返回:
db.agg_2.aggregate({$project:{test1:{$concat:["$orderAddressL",{$dateToString:{format:"%Y年%m月%d日 %H:%M:%S",date:"$orderDate"}}]}}})
再比如我将 orderAddressL 全部转为小写返回:
db.agg_2.aggregate({$project:{test1:{$toLower:"$orderAddressL"}}})
再比如我将 orderAddressL 全部转为大写返回:
db.agg_2.aggregate({$project:{test1:{$toUpper:"$orderAddressL"}}})
逻辑表达式
想要比较两个数字的大小,可以使用 $cmp 操作符,如下:
db.agg_2.aggregate({$project:{test1:{$cmp:["$freight","$discounts"]}}})
如果第一个参数大于第二个参数返回正数,第一个参数小于第二个参数则返回负数。也可以利用 $strcasecmp 来比较字符串(中文无效):
db.agg_2.aggregate({$project:{test1:{$strcasecmp:["$orderAddressL",{$dateToString:{format:"%Y年%m月%d日",date:"$orderDate"}}]}}})
至于我们之前介绍的 $eq / $ne / $gt / $gte / $lt / $lte 等操作符在这里一样是适用的。另外还有 $and、$or、$not 等表达式可用,以 $and 为例,如下:
db.agg_2.aggregate({$project:{test1:{$and:[{$eq:["$freight","$prodMoney"]},{$eq:["$freight","$discounts"]}]}}})
$and 中的每个参数都为 true 时返回 true。$or 则表示参数中有一个为 true 就返回 true。$not 则会对它的参数的值取反,如下:
db.agg_2.aggregate({$project:{test1:{$not:{$eq:["$freight","$discounts"]}}}})
另外还有两个流程控制语句,如下:
db.agg_2.aggregate({$project:{test1:{$cond:[false,"trueExp","falseExp"]}}})
$cond 第一个参数如果为 true,则返回 trueExp,否则返回 falseExp。
db.agg_2.aggregate({$project:{test1:{$ifNull:[null,"replacementExp"]}}})
$ifNull 第一个参数如果为 null,就返回 replacementExp,否则就返回第一个参数。