全文检索
什么是?
使用正则表达式模糊检索文本内容,对于大段文本来说,效率很低,而且无法理解语义
这个时候可以使用全文检索,可以快速进行文本检索,且内置多种语言分词机制,可理解语义,
MongoDB提供文本索引来支持全文检索,文本索引可以建立在任何字符串格式的键上,甚至可
以建立在以字符为元素的数组上
问题:
文本索引比普通索引可能导致更严重的性能问题:
创建时,开销较大,甚至可能导致MongoDB过载,故尽量在离线状态下创建
插入时,文本索引可能导致写入性能比一般集合要差
何时使用?
只有对大段文本检索内容时才使用全文检索
如何创建?
//创建理解语义的文本索引
db.myColl.ensureIndex({键1:"text",键2:"text"});
//对所有文本类型键创建文本索引
db.myColl.ensureIndex({"$**:"text""})
可同时在多个文本类型的键上建立符合文本索引,在检索时,可以同时检索
//全文索引 db.myColl.find({$text:{$search:'关键词1 关键词2'}}); //多个关键词之间是"或"的关系
全文索引主要依赖于特定语言的分词机制和语义,绝不等同于增则表达式的字符匹配
搜索词法
默认,使用文本索引时,search键后的多个关键词用空格分割,以OR关系分头检索
精准匹配:
//创建精准匹配
db.myColl.find({$text:{$search:'"关键词1"'}})
使用双引号包裹的关键词或词组要求必须完整匹配
实例:
db.movies.drop();
db.movies.insert([
{
title:"Titanic",
content:"I'm king of the world!",
year:1997
},
{
title:"The Godfather",
content:"Keep your friends close, but your enemies closer",
year:1972
},
{
title:"Gone With The Wind",
content:"After all, tomorrow is another day!",
year:1939},
{
title:"Forrest Gump",
content:"Miracles happen every day",
year:1994
},
{
title:"The Lion King",
content:"This is my kingdom. If I don't fight for it, who will?",
year:1994},
{
title:"Titanic",
content:"To make each day count",
year:1997
},
]);
//创建全文索引
db.movies.ensureIndex({content:"text"});
//查找
//db.movies.find({$text:{$search:'enemy'}})
//精确查询
db.movies.find({$text:{$search:'"enemies"'}})
排除匹配:
在关键词前加-
聚合
什么是?
通过一些操作符进行统计和汇总,返回我们想要的结果
聚合框架:
通过构建管道,对数据逐级筛选,投射,分组,排序的汇总和统计过程
管道操作:
1、$match
什么是?
专门对集合中的文档进行筛选
何时使用?
只要希望在聚合前过滤掉不符合条件的文档,就可以使用$match
通常,$match用于聚合管道的第一步筛选:
好处:
1、可减少统计的数据量;
2、在投射和分组前筛选还可以应用索引
如何使用?
db.myColl.aggregate({$match:{查询文档}})
优化:在数据进入管道前,就过滤掉尽量多的文档,可以极大提高管道操作的效率
实例:
db.emps.drop();
for(var i=0,arr=[];i<100000;i++){
arr.push({
ename:"e"+(100000+i+"").slice(1),
address:{
city:"city"+(1000+parseInt(Math.random()*100)+"").slice(1),
street:"street"+(10000+parseInt(Math.random()*1000)+"").slice(1)
}
});
}
db.emps.insert(arr);
//筛选
db.emps.aggregate({$match:{'address.city':'city014'}})
2、$project
什么是?
最简单的$project操作就是从文档中选择想要的键。选取键时,可以指定返回或者不返回哪个键
何时使用?
如果聚合只针对集合中文档的个别键进行汇总,就可以先用$project仅选择需要的键
如何使用?
db.myColl.aggregate({$project:{键1:1,键2:0}});
可以为投射出的键重命名:
db.myColl.aggregate({$project:{别名:"$原键",键:1,...}})
其中:$原键表示,让新别名引用原键的意思,$后面跟的是原键名
db.emps.drop();
for(var i=0,arr=[];i<100000;i++){
arr.push({
ename:"e"+(100000+i+"").slice(1),
address:{
city:"city"+(1000+parseInt(Math.random()*100)+"").slice(1),
street:"street"+(10000+parseInt(Math.random()*1000)+"").slice(1)
}
});
}
db.emps.insert(arr);
//从结果集中指定要返回的键(类似find方法第二个参数)
db.emps.aggregate(
{$project:{city:'$address.city'}})
算数表达式
什么是?
专门对指定键执行算数计算的表达式
何时使用?
只要希望对选取的键进行计算后再统计时
操作符:
$add 加,$subtract 减,$multiply 乘,$divide 除,$mod 取余
如何使用?
{$project:{别名:{
$算数操作:[
"$键1"/值1,
"$键2"/值2,
...]
}
}
}
注:
因为计算后的键值和原键值肯定不一样,所以通常都需要其别名
参与算数表达式的键或直接量,顺序放在算数操作符后的数组中
例:
计算每个员工的工资和奖金的和
$add:["$salary","bonus"]
等价于$salary+$bonus
对键名执行算数运算,等效于对集合中每个文档的对应键值都很执行相同的算数操作
实例:
db.emps.drop();
for(var i=0,arr=[];i<100000;i++){
arr.push({
ename:"e"+(100000+i+"").slice(1),
address:{
city:"city"+(1000+parseInt(Math.random()*100)+"").slice(1),
street:"street"+(10000+parseInt(Math.random()*1000)+"").slice(1)
}
});
}
db.emps.insert(arr);
//从结果集中指定要返回的键(类似find方法第二个参数)
db.emps.aggregate(
{$project:{city:'$address.city'}})
//算数运算表达式
db.movies.aggregate(
{$project:
{
myYear:{
$subtract:[
2018,
'$year'
]
}
}
}
)
算术表达式的嵌套
什么是?
算术表达式可以多级嵌套,实现复杂的算数计算
一个算数表达式的结果,可以继续作为另一个外层算数运算的参数值之一
如何使用?
{$project:{ 别名:{
$算数运算2:[
{$算数运算1:["$键1"/值1,"$键2"/值2]},"$键3"/值3
]
}}}
实例:
db.emps.drop();
for(var i=0,arr=[];i<100000;i++){
arr.push({
ename:"e"+(100000+i+"").slice(1),
address:{
city:"city"+(1000+parseInt(Math.random()*100)+"").slice(1),
street:"street"+(10000+parseInt(Math.random()*1000)+"").slice(1)
}
});
}
db.emps.insert(arr);
//从结果集中指定要返回的键(类似find方法第二个参数)
db.emps.aggregate(
{$project:{city:'$address.city'}})
//算数表达式的嵌套
//需求 从学生集合中,计算数学成绩和语文成绩的分数之和,并计算平均值(除以2)
db.stus.aggregate({
$project:{
averageScore:{
$divide:[
{
$add:
['$math', '$chinese']
},
2
]
}
}
})
日期表达式
什么是?
日期表达式专用于获取日期类型键值或日期对象的指定分量的值
何时使用?
只要按照日期类型的键值进行统计时,都需要用日期表达式取出指定分量,再计算或者统计
如何使用?
{$日期操作符:"$日期类型值"/日期对象}
日期操作符
$year,$month,$week,$dayOfWeek,
$dayOfMonth,$dayOfYear,$hour,$minute
$second
字符串表达式
什么是?
字符串表达式专门对指定键的值执行字符串操作
何时使用?
只要希望再统计前对键值执行字符串操作时,就用字符串表达式
字符串操作符包括:
$substr:专门负责获取键值中的字符串
$substr:["$键" /值,开始位置,要获取的字符个数]
$concat:专门负责将多个子字符串拼接为一个完整字符串
$concat:["$键"/值1,"$键"/值2,...]
实例:
db.products.drop();
db.products.insert([
{
"pname" : "iphone6",
"count" : 80.0,
"createdDate" : ISODate("2015-01-20T00:00:00.000+08:00"),
"random" : 0.561293853912503,
"RAM" : "64G"
},
/* 2 */
{
"pname" : "iphone6plus",
"count" : 68.0,
"createdDate" : ISODate("2015-06-12T00:00:00.000+08:00"),
"random" : 0.561293853912503,
"RAM" : "128G"
},
/* 3 */
{
"pname" : "iphone6s",
"count" : 35.0,
"createdDate" : ISODate("2016-06-04T14:59:23.409+08:00"),
"random" : 0.561293853912503,
"RAM" : "16G"
},
/* 4 */
{
"pname" : "iphone6splus",
"count" : 36.0,
"createdDate" : ISODate("2016-06-04T14:59:23.411+08:00"),
"random" : 0.561293853912503,
"RAM" : "128G"
},
/* 5 */
{
"pname" : "iphoneSE",
"count" : 50.0,
"random" : 0.561293853912503,
"RAM" : "16G"
}
]);
db.products.aggregate({
$project:{
spec:{
$concat:[
{
$substr:[
'$pname',6,6
]
},
" ",
"$RAM"
]
}
}
})
比较和关系表达式
什么是?
MongoDB中的逻辑表达式和条件运算
何时?
只要希望根据键值判断条件是否成立,或者夕阳根据不同条件返回不同的值,就用逻辑表达式
如何?
$cmp:["$键1"/值1,"$键2"/值2]
如果前>后,则返回证书,否则如果前<后,则返回负数,否则返回0
比较任意两个字符串类型的键/值得大小关系
$strcasecmp:["$键1"/值1,"$键2"/值2]
关系表达式:
简单判断两键/值得大小,包括:
$eq,$ne,$gt,$gte,$lt,$lte:["$键1"/值,"$键2"/值2]
如果满足条件,则返回true,否则就返回false
逻辑表达式
什么是?
将多个关系表达式的结果综合起来得出的最终结论
$and/$or/$not:[关系表达式1,... ...]
实例:查询stus中语文成绩>90和数学成绩>90的数据
db.stus.aggregate({ $project:{ goodStu:{ $and:[ {$gte:['$chinese',90]}, {$gte:['$math',90]} ] } } })
实例:查询stus中语文成绩>90或数学成绩>90的数据
db.stus.aggregate({ $project:{ goodStu:{ $or:[ {$gte:['$chinese',90]}, {$gte:['$math',90]} ] } } })
实例:查询stus中语文成绩不<=90,不数学成绩<=90的数据
db.stus.aggregate({ $project:{ goodStu:{ $not:[ {$lte:['$chinese',90]}, {$lte:['$math',90]} ] } } })
条件表达式
什么是?
根据不同的条件返回不同的值
$cond
相当于程序中的条件运算的原理
如何使用?
$cond:[bool键/值,值1,值2]
//如果条件为true,则返回值1,否则返回值2
实例:查找语文成绩大于80的学生返回优秀否则良好,判断是否大于60分如果大于60为良否则为不及格
db.stus.aggregate({
$project:{
pass:{
$cond:[
{$gt:['chinese',80]},
'优秀',
'良好'
$cond:[
{$gt:['$chinese',60]},
'良',
'不及格'
]
]
}
}
})
$ifNull 专门用于在值为null时自动提供默认值
$ifNull:["$键"/值,替换值]
$group
什么是?
分组就是将前一步提取出的键值,按指定范围分组
何时使用?
专门用于分组统计
作用?
将前一步提取出的键值,按指定范围分组
如何使用?
选择了要分组的键,就要将键交给$group分组的_id键
{$group:{_id:"$键"/值}}
也可以按照多个键分组
{$group:{_id:{别名1:"$键1",别名:"$键2"}}}
实例:
//分组统计,按部门统计
db.tasks.aggregate({
$group:{
_id:'$dept'
}
})
算数操作符
什么是?
算数操作符专门对分组后的键值执行算数操作
包括?
$sum求和,$avg求平均
如何使用?
{$group:{_id:"$键"/表达式,别名:{$算数操作符:"$键"/值}}}
$sum
作用:
既可以统计数量,又可以求和:
$sum:"$键"/值,对给定的键值或者表达式的值求和
$sum:1,表示吧分组内的每条记录都当做1,这就相当于统计个数
(没有$count,$sum:1就相当于统计个数)
实例:
//分组统计,按部门统计,通过$sum:1求和并保存在$group中
db.tasks.aggregate({
$group:{
_id:'$dept',
count:{$sum:1}
}
})
$age
作用:
专门计算平均数。相当于对数组内的数据求和以后,再除以组内的文档个数
如何使用?
$avg:"$键"/值
值为null或者不包含计算键的文档,不包括在计算平均的分母中
实例:
//$avg计算平均值
db.scores.aggregate({
$project:{
class:1, //需要班级
score:1 //需要成绩
},//聚合操作
//按照班级进行分组
},
{
$group:{
_id:'$class',
//计算成绩的平均值
avgScore:{
$age:'$score'
}
}
}
})
极值操作符
作用:
极值操作符专门用于获得一个分组范围内的最大值,最小值,第一个值和最后一个值
包括:
$max最大值,$min最小值,$first分组中第一个值,$last分组中最后一个值
注:其中$first和$last只有在配合排序操作时才有意义
强调:
值为null或者不包含计算键的文档,不参与最大值或者最小值的比较
多数情况下,$max和$min效果上等效于先排序再获得$first和$last,但是$max和$min显然效率更高
$sort
作用:专门负责将统计的结果排序
如何使用
{$sort:{"$键1":1,"$键2":-1,...}}
值为1 - 升序排列,值为-1 - 降序排列
注:
先后按照多个键排序时,生成的_id键是一个内嵌文档,多个分组的键都是内嵌文档的键。所以,排序时要用.运算符进入_id键
的内部,才能访问到排序的两个键
实例:
//从tasks集合中,按照部门分组,同时希望降序并且分页查询,一页5调数据
var page = 1; //分页
ab.tasks.aggregate({
{ $group:{
_id:'$dept' //按照部门排序
}
},
{
$sort:{_id:-1} //降序排列
},
{
$skip:(page-1)*5 //每次跳过(page-1)*5条数据
},
{
$limit:5 //5条显示
}
})
//0 - 5
//5 - 10
//10 - 15
注意:
$sort可以按照需求写在分组前,或者分组后,但是必须符合操作的逻辑:
比如:获得tasks集合中,每个部门最新的一项任务编号:就应该先排序,再获得任务编号
再比如:统计每个班的最高分,按照最高分降序排列:必须先统计出最高分,才能按统计结果的最高分排序
$limit和$skip
$limit:
专门限制返回结果的个数,只接受一个数字n作为值。表示仅返回n个结果
$skip:
专门制定跳过的记录格式。只接受一个数字n做为跳过的记录数
强调:
管道操作讲究顺序,$skip操作必须在$limit操作之前
和查询中的skip操作一样,聚合中的$skip同样需要先查询出所有,再丢弃,所以效率比较低
实例同上,结合
$unwind
作用:
专门用于提取文档中的内嵌再数组类型键内部的子文档
何时:
当希望将所有文档内的内嵌再数组中的子文档当做普通文档看待,并统计时都要先用$unwind提取出来
实例:将weixin中的_id进行降序
if(db.getCollectionNames().indexOf("weixin")!=-1)
db.weixin.drop();
for(var i=0,posts=[];i<10000;i++){
posts.push({
uname:"user"+parseInt(Math.random()*(90-10+1)+10),
content:"you jump I jump"+i,
comments:[
{uname:"user"+parseInt(Math.random()*(90-10+1)+10),
content:Math.random()<0.5? "you can you up":"no zuo no die",
},
{uname:"user"+parseInt(Math.random()*(90-10+1)+10),
content:Math.random()<0.5?"you can you up":"no zuo no die",
},
]
});
}
db.weixin.insert(posts);
db.weixin.aggregate({
{$unwind:'$comments'}, //转为普通文档格式进行分组
{
$group:{
_id:'$comments.user',
count:{$sum:1} //求和
}
},{
$sort:{count:-1} //做降序
}
})
MapReduce
原理:
用JavaScript执行汇总和统计的过程
阶段:
1、Map阶段:用JavaScript对每个传入的文档内容按照指定逻辑计算得出一个结论
2、Reduce阶段:还是用JavaScript将Map阶段为每个文档计算出的结果汇总,统计出最终结果
优点:
有助于将复杂问题分段简化,反而降低了聚合运算的难度
实例:第一个MapReduce操作:
需求:统计所有文档中,a和b两个键值的和
第一步:定义map阶段的任务函数
db.nums.drop();
for(var i=0,arr=[];i<10;i++){
arr[i]={
a:parseInt(Math.random()*9+1),
b:parseInt(Math.random()*9+1)
}
}
db.nums.insert(arr);
//自定义map
function map(){
//针对集合中的每个文档中的a和b求和
var subTotal = this.a + this.b;
emit('subTotal',subTotal);
}
//执行完map会产生一个中间集合
//[{subTotal:7},{subTotal:13}等等]
第二步:定义reduce
db.nums.drop();
for(var i=0,arr=[];i<10;i++){
arr[i]={
a:parseInt(Math.random()*9+1),
b:parseInt(Math.random()*9+1)
}
}
db.nums.insert(arr);
//自定义map
function map(){
//针对集合中的每个文档中的a和b求和
var subTotal = this.a + this.b;
emit('subTotal',subTotal);
}
//执行完map会产生一个中间集合
//[{subTotal:7},{subTotal:13}等等]
//自定义reduce
function reduce(key,emites){
//key:subTatle
//emits:真正的数据->中间集合
//计算总和
var total = value.reduce(/*箭头函数*/(prev,elem)=>{
return prev+elem;
})
return total;
}
第三步:定义reduce阶段的任务函数:
db.nums.drop();
for(var i=0,arr=[];i<10;i++){
arr[i]={
a:parseInt(Math.random()*9+1),
b:parseInt(Math.random()*9+1)
}
}
db.nums.insert(arr);
//自定义map
function map(){
//针对集合中的每个文档中的a和b求和
var subTotal = this.a + this.b;
emit('subTotal',subTotal);
}
//执行完map会产生一个中间集合
//[{subTotal:7},{subTotal:13}等等]
//自定义reduce
function reduce(key,value){
//key:subTatle
//value:真正的数据->中间集合
//计算总和
var total = value.reduce(/*箭头函数*/(prev,elem)=>{
return prev+elem;
})
return total;
}
//mapReduce 调用系统提供方法
ab.nums.mapReduce(map,reduce,{out:'nums_total'/*将综合输出到nums_total*/});
//创建了一个集合nums_total
//去集合中看结果
关于mapReduce返回的结果对象: