一、聚合
1、Count
1.1、请查询person中美国学生的人数。
db.person.find({country:"USA"}).count();
2、Distinct
示例:
2.1、请查询出person中一共有多少个国家,分别是什么?
db.runCommand({distinct:"person",key:"country"}).values;
3、Group
语法:
db.runCommand({group:{
ns:集合名字,
key:分组的键对象,
initial:初始化累加器,
$reduce:组分解器,
Condition:条件,
Finalize:组完成器
}})
分组:
首先会按照key进行分组,每组的每一个文档全要执行$reduce的方法,
它接收2个参数,一个是组内本条记录,另一个是累加器数据。
示例:
3.1、请查出person中每个国家学生数学成绩最好的学生信息(必须在90以上)
db.runCommand({group:{
ns:"person",
key:{"country",true},
initial:{m:0},
$reduce:function(doc,prev){
if(doc.m>prev.m){
prev.m=doc.m;
prev.name=doc.name;
prev.country=doc.country;
}
},
condition:{m:{$gt:90}}
}})
3.2、Finalize完成器的使用
示例:
在3.1要求基础之上,把每个人的信息连接起来,写一个描述赋值给m。
db.runCommand({group:{
ns:"person",
key:{"country",true},
initial:{m:0},
$reduce:function(doc,prev){
if(doc.m>prev.m){
prev.m=doc.m;
prev.name=doc.name;
prev.country=doc.country;
}
},
condition:{m:{$gt:90}},
finalize:function(prev){
prev.m=prev.name+" Math Score "+prev.m
}
}})
3.3、用函数格式化分组的键
如果集合中出现键country和counTry同时存在,分组如何解决?
示例:
准备数据:向person集合插入一条记录(新增一列counTry,注意大小写)
db.person.insert({
name:"USPCAT",
age:25,
email:[email protected],
c:80,
m:100,
e:85,
counTry:"China",
books:{"JAVA","C#","Oracle"}
});
在3.2的要求基础上,不区分列country大小写进行分组查询。
db.runCommand({group:{
ns:"person",
$keyf:function(doc){
if(doc.country){
return doc.country;
}
else{
return doc.counTry;
}
},
initial:{m:0},
$reduce:function(doc,prev){
if(doc.m>prev.m){
prev.m=doc.m;
prev.name=doc.name;
if(doc.country){
prev.country=doc.country;
}
else{
prev.country=doc.counTry;
}
}
},
condition:{m:{$gt:90}},
finalize:function(prev){
prev.m=prev.name+" Math Score "+prev.m
}
}})
4、MapReduce
count,distinct,group能做的事情MapReduce都能做,它是一个可以轻松并行化到多个服务器的聚合方法。
它会拆分问题,再将各个部分发送到不同机器上,让每台机器完成一部分.当所有机器都完成时候,再把结果汇集起来形成
最终完整的结果。
MapReduce需要几个步骤:
(1).映射(map):将操作映射到集合中的每个文档.这个操作要么什么都不做,要么产生一个键和n个值。
(2).洗牌(shuffle):按照键分组,并将产生的键值组成列表放到对应键中。
(3).化简(reduce):把列表中的值 化简 成一个单值,这个值被返回。
(4).重新洗牌(reshuffle):直到每个键的列表只有一个值为止,这个值就是最终结果。
MapReduce的速度比group慢,group也很慢.在应用程序中,最好不要用MapReduce,可以在后台运行MapReduce
创建一个保存结果的集合,可以对这个集合进行实时查询。
示例:
4.1、找出集合中的所有键
MongoDB没有模式,所以并不知晓每个文档有多少个键,通常找到集合的所有键的做好方式是用MapReduce。
在映射阶段,想得到文档中的每个键,map函数使用emit 返回要处理的值。emit会给MapReduce一个键和一个值。
这里用emit将文档某个键的记数(count),返回({count:1})。我们为每个键单独记数,所以为文档中的每一个键调用一次emit,
this是当前文档的引用:
map=function(){
for(var key in this)
{
emit(key,{count:1})
}
};
这样返回了许许多多的{count:1}文档,每一个都与集合中的一个键相关.这种有一个或多个{count:1}文档组成的数组,
会传递给reduce函数.reduce函数有两个参数,一个是key,也就是emit返回的第一个值,另一个参数是数组,由一个或者多个
对应键的{count:1}文档组成.
reduce=function(key,emits){
total=0;
for(var i in emits){
total+=emits[i].count;
}
return {count:total};
}
reduce要能被反复被调用,不论是映射环节还是前一个化简环节.reduce返回的文档必须能作为reduce的
第二个参数的一个元素.如x键映射到了3个文档{"count":1,id:1},{"count":1,id:2},{"count":1,id:3}
其中id键用于区别.MongoDB可能这样调用reduce:
>r1=reduce("x",[{"count":1,id:1},{"count":1,id:2}])
{count:2}
>r2=reduce("x",[{"count":1,id:3}])
{count:1}
>reduce("x",[r1,r2])
{count:3}
reduce应该能处理emit文档和其他reduce结果的各种集合.
如:
mr=db.runCommand(
{
"mapreduce":"user",
"map":map,
"reduce":reduce,
"out":{inline:1}
}
)
或:
db.user.mapReduce(map,reduce,{out:{inline:1}})
"timeMillis" : 5,//操作花费的时间
"counts" : {
"input" : 10,//发往到map函数的文档个数
"emit" : 40,//在map函数中emit被调用的次数
"reduce" : 4,//在map函数中reduce被调用的次数
"output" : 4//结果集合中创建的文档数量.
},
(1).mapreduce是根据map函数里调用的emit函数的第一个参数来进行分组的。
(2).仅当根据分组键分组后一个键匹配多个文档,才会将key和文档集合交由reduce函数处理。
注意:MongoDB 1.8版本以上,必须指明 out 参数 。
否则会报如下错误:
"assertion" : "'out' has to be a string or an object",
"assertionCode" : 13606,
MapReduce中的其他键
mapreduce,map,reduce这三个键是必须的,MapReduce命令还有其他的可选键
finalize:函数
将reduce的结果发送给这个键,这是处理过程的最后一步
keeptemp:布尔值
连接关闭时,临时结果是否保存
output:字符串
结果集合的名字,设定该项则隐含着keeptemp:true
query:文档
会在发往map函数前,先用指定条件过滤文档
sort:文档
会在发往map函数前先给文档排序
limit:整数
发往map函数文档的最大数量
scope:文档
javascript代码中要用到的变量
verbose:布尔值
是否产生更加信息的服务器日志
二、数据库命令操作
1、命令执行器runCommand
1.1、用命令执行完成一次删除集合的操作
db.runCommand({drop:"person"})
执行结果:
{
"nIndexesWas" : 1,
"msg" : "indexes dropped for collection",
"ns" : "foorbar.person",
"ok" : 1
}
2、如何查询MongoDB为我们提供的命令
2.1、Mongodb启动命令mongod参数说明:
http://www.uspcat.com/forum.php?mod=viewthread&tid=7722&extra=page%3D1
2.2、在shell中执行db.listCommands();
2.3、访问网址:http://localhost:28017/_commands
(注意:一般情况下,网址端口号在数据库端口号的基础上加1000;启动数据库时,必须开启简单的rest API,才能访问API网址。)
3、常用命令示例
3.1、查询服务器版本号和主机操作系统信息。
db.runCommand({buildinfo:1});
3.2、查询执行集合的大小,空间,索引等详细信息。
db.runCommand({collStats:"books"});
3.3、查询操作本集合最后一次的错误信息。
db.runCommand({getLastError:"map"});
返回结果:
{ "n" : 0, "connectionId" : 5, "err" : null, "ok" : 1 }
三、固定集合
1、固定集合的概念:容量大小不会改变的集合。
如下图所示:
2、固定集合的特性
(1)、如果空间不足,插入新文档时,会自动删除最早的记录;
(2)、在固定集合中,不允许手工删除文档;
(3)、导致文档位置发生变化的更新操作,将会被拒绝;
(4)、默认情况下,固定集合没有索引,"_id"也是没有索引的,当然,索引可以手工创建;
(5)、由于固定集合不需要分配新的空间,所以它的插入速度是非常的快;
(6)、由于固定集合的顺序是确定的,所以它的查询速度是非常的快;
(7)、最适合的应用就是日志管理。
3、创建固定集合
3.1、创建一个新的固定集合,要求:大小是100字节,可以存储10个文档。
db.createCollection("my_collection",{size:100,capped:true,max:10});
3.2、把一个普通集合转换成固定集合
db.runCommand({convertToCapped:"person",size:100000});
4、自然排序(文档在磁盘上的物理排序)
$natural取值为1时,表示与默认顺序相同,取值为-1时,则刚好相反。
4.1、查询固定集合mycollection并且反向排序
db.mycollection.find().sort({$natural:-1});
5、尾部游标(shell不支持,java和php等驱动是支持的)
5.1、尾部游标的概念
这是个特殊的只能用到固定集合上的游标,它在没有结果的时候,
也不会自动销毁,它是一直等待结果的到来。
当关联的集合内有新文档被添加时,尾部游标就会被触发。
5.2、特点:
连接持久化:在明确指定的情况下,不会自动关闭。
四、GridFS文件系统
1、GridFS文件系统的概念
GridFS是MongoDB提供的用于存储大型二进制数据的机制。
它本身就是一个分布式文件系统:
(1)、GridFS会直接利用已建立的复制、分片机制;
(2)、GridFS可以避免传统文件系统的某些弊端,例如:同一目录下文件数量过多;
(3)、GridFS不会产生磁盘碎片。
2、GridFS文件系统的原理
{"_id":ObjectId("..."),
"n":0,
"data":BinData("..."),
"files_id":ObjectId("...")
}
GridFS是建立在MongoDB普通文档基础之上的轻量级的分布式文件存储规范。
它的基本思想是,将大型文件切分成很多小块,每一块作为一个单独的文档存储。
在GridFS规范中定义了很多额外的键,最值得关注的是md5这个键,利用它可以做很多有趣的事情。
3、使用GridFS
3.1、查看GridFS的所有功能
cmd->mongofiles
3.2、上传一个文件
mongofiles -d foorbar -l "D:\Work\MongoDB\GridFS测试.txt" put "GridFSTest.txt"
3.3、查看GridFS文件的存储状态
利用VUE查看,如下图所示:
集合查看
db.fs.chunks.find()和db.fs.files.find()存储文件系统的所有文件信息。
(1)> db.fs.chunks.find()
{ "_id" : ObjectId("51fe10e7fdf286d0aec9cf1c"), "files_id" : ObjectId("51fe10e7dff0527f1beb1819"), "
n" : 0, "data" : BinData(0,"R3JpZEZTsuLK1EdyaWRGU7LiytRHcmlkRlOy4srUR3JpZEZTsuLK1EdyaWRGU7LiytQ=") }
(2)> db.fs.files.find()
{ "_id" : ObjectId("51fe10e7dff0527f1beb1819"), "chunkSize" : 262144, "filename" : "GridFSTest.txt",
"length" : NumberLong(50), "md5" : "13a8cb2df1be313f2e931ea854f868ab", "uploadDate" : ISODate("2013
-08-04T08:29:27.629Z") }
3.4、查看文件内容( VUE中可以查看,shell无法打开文件)
mongofiles -d foorbar get "GridFSTest.txt"
3.5、查看所有文件
mongofiles -d foorbar list
3.6、删除已经存在的文件(VUE中操作)
mongofiles -d foorbar delete "GridFSTest.txt"
五、服务器端脚本
1、Eval
利用db.eval()可以在MongoDB的服务器端执行任意JavaScript脚本。
用处有很多,比如事务模拟。
重点要提的就是,如果传入的JavaScript脚本过大,会给调试代码很多不便,此时可以将调试信息写入到数据库日志中。
方法是在脚本中调用如下语句:
db.eval("print('Hello World!');")
1.1、服务器端运行eval
db.eval("function(name){return name}","uspcat")
2、Javascript的存储
存储JavaScript就类似于sql数据库中的存储过程。
可以将JavaScript脚本(js变量或者函数)保存在服务器端,需要的时候可以使用前面提到的db.eval()调用。
示例:
(1)、把变量加载到特殊集合system.js中;
db.system.js.insert({"_id":"name",value:"uspact"})
(2)、调用
db.eval("return name;");
六、数据库引用(DBREF)
1、语法:
{"$ref":"集合名称","$id":"唯一标识键","$db":"数据库"}
2、概念
DBRef是作为内嵌文档来存在的,它的作用就相当于html标签中a标签的和href特性。
DBRef可以跨数据库引用文档,就如上面的代码演示的那样。
注意:
以上代码中键的顺序是不能改变的,必须是这样的顺序,只有$db键是可选的。
3、示例:
创建一个用户user集合:
创建一个笔记note集合;
笔记可以引用用户或者别的笔记。
(1)、给user集合添加数据
db.user.insert({"_id":"Make","display_name":"Make D"});
db.user.insert({"_id":"Kristina","display_name":"Kristina C"});
(2)、给note集合添加数据
db.note.insert({"_id":5,"author":"Make","text":"MongoDB is fun!"});
db.note.insert({"_id":6,"author":"Kristina","text":"... and DBrefs are easy,too!",
"references":[{"$ref":"user","$id":"Make"},{"$ref":"note","$id":5}]
});
(3)、查询笔记note集合引用的数据
var note=db.note.findOne({"_id":6});
note.references.forEach(function(ref){
printjson(db[ref.$ref].findOne({"_id":ref.$id}));
});
执行结果:
{ "_id" : "Make", "display_name" : "Make D" }
{ "_id" : 5, "author" : "Make", "text" : "MongoDB is fun!" }