mongoDB-特殊索引

全文检索

什么是?

    使用正则表达式模糊检索文本内容,对于大段文本来说,效率很低,而且无法理解语义

    这个时候可以使用全文检索,可以快速进行文本检索,且内置多种语言分词机制,可理解语义,

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返回的结果对象:


发布了46 篇原创文章 · 获赞 8 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章