文档和集合
文档
文档是Mongodb中的最基本数据单元,类似于关系数据库中的行。
在mongodb中,文档的键通常都是字符串。Mongodb对文档的键是区分大小写的,对键值是区分数据类型的
例如:
{username:”admin”}和{userName:”admin”} {age:20}和{age:”20”}是不同的文档
特别注意:{username:”admin”,username:”非法键”},同一文档中 键值是不能重复的。
集合
集合是文档组成的,在关系型数据库中就是表。
在mongodb中,对集合中存储的文档是非常开放的,一个集合中可以存放各式各样的文档。
例如:
{username:”admin”} , {role:”admin”} (强烈反对)
{username:”admin”},{username:”pp”} (强烈推荐)
子集合
组织子集合的一种惯例是使用“.”字符分开的按命名空间划分的子集合。在MongoDB中使用子集合来组织数据是很好的方法。
例如:
用户与用户详细信息包含两个集合,分别是user.user和user.userinfo。这样做的目的只是为了使组织结构更好些,也就是说 user这个集合(这里根本就不需要存在)及其子集合没有任何关系。把数据库的名字放到集合名前面,得到就是集合的完全限定名,称为命名空间。
数据库
admin
从权限的角度来看,这是“root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local
这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config
当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
命名规则
1、不能是“”空字符串
2、不可以含有空格,在mongodb中 空格代表结束字符。
3、文档的键不可以有“.”和“$”符号,集合名中不可以包含“$”
4、集合不能以 “system.”开头,mongodb中system是系统保留前缀
5、集合名长度不得超过121字节,在实际使用当中应该小于100字节;数据库名最多不超过64个字节
(mongodb最终是以文件方式存储的,操作系统中的非法文件名,在mongodb中就是非法的,这样便于理解与记忆)
数据类型
MongoDB的文件存储格式为BSON,同JSON一样支持往其它文档对象和数组中再插入文档对象和数组,同时扩展了JSON的数据类型.与数据库打交 道的那些应用。
String 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Array 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于嵌入式的文档,即一个值为一个文档
Null 用于创建空值。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。
insert
db.集合名称.insert(document)
这个操作是向mydb.user集合中增加“_id”建,并添加到MongoDB中。检验是否含有“_id”键和文档不超过16M,除之,不做任何数据验证。
save
db.mydb.save({name:”test2”})
这个操作也是向mydb.user集合中增加“_id”建,并添加到MongoDB中。save函数实际就是根据参数条件,调用了insert或update函数.如果想插入的数据对象存在,insert函数会报错,而save函数是改变原来的对象;如果想插入的对象不存在,那么它们执行相同的插入操作.这里可以用几个字来概括它们两的区别,即所谓"有则改之,无则加之".
update
1. db.mydb.user.update({name:”test1”} , {name:”test1”,age:20})
2. 使用findOne()函数: user=db.mydb.user.findOne({name:”test1”})
user.name=“test1”
user.age++
db.mydb.user.update({_id:user._id},user)
修改器
$inc修改器
update({键:值},{$inc:{键:值}})
db.mydb.user.update({users:"dsm"},{$inc:{age:1}})
age自增1(age必须为数字类型)
$set修改器
update({键:值},{$set:{键:值}})
set为该文档设置一条属性,如果没有则创建一条属性
db.mydb.user.update({users:"dsm"},{$set:{hobby:"皮"}})
也可以修改内嵌文档
db.mydb.user.update({users:"dsm"},{$set:{"skills.name":["正方形打野","边缘OB","假设性原则"]}})
db.mydb.user.update({users:"dsm"},{$set:{"skills.发动结果":["队友崩盘","永不团灭","马半仙模式"]}})
修改内嵌文档不同于修改属性,不会覆盖。
$unset修改器
update({键:值},{$unset:{键:-1/0/1}})
-1/0/1都表示删除
删除该文档的一条属性
db.mydb.user.update({users:"dsm"},{$unset:{"skills.基地爆炸":-1}})
$push数组修改器
update({…},{$push:{键:值}})
$push修改器是会往键已有的数组中追加值,如果数组不存在,则创建新的数组并更新键值。
db.mydb.user.update({users:"dsm"},{$push:{hobby:"养大象"}})
$push + $ne数组修改器
update({键:{$ne:值}},{$push:{键:值}})
$push+$ne 组合键使用于如果追加值不在数组中则追加进去。
db.mydb.user.update({users:"dsm",hobby:{$ne:"折磨王"}},{$push:{hobby:"折磨王"}})
addToSet数组修改器
update({…},{$addToSet:{键:值}})
相当于$push+$ne
db.mydb.user.update({users:"dsm"},{$addToSet:{hobby:"折磨王"}})
db.mydb.user.update({users:"dsm"},{$addToSet:{hobby:"拳皇"}})
$addToSet + $each数组修改器
update({…},{$addToSet:{键:{$each:[…]}}})
当我们一次要追加多个值得时候,而且需要判断数组中是否存在,这时$push+$ne 组合就不能实现了,需求用到$addToSet+$each组合键。
db.mydb.user.update({users:"dsm"},{$addToSet:{hobby:{$each:["马氏跑动杀","葵花三式","折磨王"]}}})
只加入了马氏跑动杀和葵花三式,折磨王没有加入
$pop数组修改器
{$pop:{键:1}} - 从数组尾部删除一个元素
{$pop:{键:-1}} - 从数组头部删除一个元素
“$pop”修改器用于删除数组里面值,如果把数组看成一个队列或者栈的话,可以从数组任何一端删除元素。
db.mydb.user.update({users:"dsm"},{$pop:{hobby:1}})
$pull数组修改器
{$pull:{键:值}}
“$pull”修改器也是用于删除数组里面值,它可以删除数组中任意位置的值,并且删除所有匹配到的值
db.mydb.user.update({users:"dsm"},{$pull:{hobby:"折磨王"}})
$定位修改器
“$”定位修改器也是用于数组里面值的数据类型为内嵌文档,可以使用$实行定位修改。
db.mydb.user.update({users:"dsm"},{$set:{"skills.name":["萧峰","胡里亚","马化腾"]}})
db.mydb.user.update({"skills.test" :11},{$set:{"skills.name":["跑之小跳"]}})
upsert
update({匹配文档},{更新文档},true)
Upsert 可以理解为 update与insert的缩写 ,它是update函数的第三个参数,其实际含义就是更新或插入。
Update参数upsert默认值是fasle,它是一种特殊的更新。根据这个参数值,update函数判断对匹配不到的文档,将更新文档基于匹配文档之上以一个新的文档是否增加到集合中。
db.mydb.user.update({users:"pdd"},{users:"pddd"},true)
mutli
update({…},{…}, false, true)
它是update函数的第四个参数,其实际含义就是是否实现多更新操作。
update函数的mutli默认值是false, 代表只对第一个匹配到的文档进行更新操作;需要更新多个符合匹配条件的文档,则要设置mutli值为true.
例如:所有名称为test3的年龄加2
db.mydb.user.update({name:”test3”},{$inc:{age:2}, false, true})
db.runCommand({getLastError:1})可以查看受影响的文档数
返回更新文档
用法:db.集合名.findAndModify({
query:{匹配文档},
update:{更新文档}
[,remove:布尔值,sort:{排序文档},new:布尔值]
})
query: 匹配文档,也就是查询条件
sort: 排序匹配文档的条件
update: 修改器文档,执行文档更新操作
remove: true/false,是否对匹配到的文档进行删除
new: true/false, 表示返回的是更新前还是更新后的文档,默认值是false
update 与remove 必须而且只能存在一个。
它一次只能操作一个文档,不能进行upsert操作,只能更新已存在的文档。
findAndModify 执行效率有点低,话虽这么说,不代表避免使用,mongo每一个函数的存在,都有其实际存在的价值。它的执行效率相当于 一次查询、更新外加getLastError 顺序执行的时间。
db.mydb.user.findAndModify({query:{users:"dsm"},update:{$pop:{hobby:1}}})
安全操作
mongoDB 开发者采用不安全模式作为默认选择,这是由于他们与关系型数据库打交道的经验所致的,很多构建在关系型数据库的应用,都不关心返回的代码,也不会处理这个返回码,但又得苦苦等待这个返回码,这会造成性能极大的下降。mongoDB可以让用户来选择采用何种方式。有些操作可以使用不安全模式(速度快),有些操作使用安全模式。
安全操作是在执行完操作后立即执行getLastError命令,来检查是否成功执行。然后适当的处理数据库返回的错误,一般情况下数据库会抛出一个可捕获的错误,我们可以采用自己的开发语言来捕获和处理。如果执行成功,getLastError会给出额外的信息作为响应(比如:更新或删除操作给出的更新数)。
请求和连接
数据库会为每个mongoDB连接创建一个独立的队列,来存放连接的请求,当客户端发送请求,会被存放到该连接队列的末尾,当队列的请求都执行完毕后,才会执行后续的请求。对于实际应用的交错插入、查询,会产生秒级的延迟。
查询
查询 find
查询就是返回一个集合中文档的子集,子集合的范围从0个文档到整个集合。
例如:
db.mydb.user.find({name:”test1”})
指定返回值
有时并不需要返回文档中的所有键和值,遇到这样的情况,使用find 的第二个参数来指定返回键,这样做既可以节省传输的数据量,也可以节省客户端解码文档的时间和内存开销。
例如: db.mydb.user.find({},{age:1,_id:0})
1代表显示,0代表不显示
限制
查询还是有些限制的,数据库所关心的查询文档的值必须是常量,也就是所不可以使用文档的其他键值
条件查询
查询条件
类型 描述 举例
< $t (小于) db.mydb.user.find({age:{$lt:35}})
<=$lte (小于等于) db.mydb.user.find({age:{$lte:35}})
> $gt (大于) db.mydb.user.find({age:{$gt:31}})
>=$gte (大于等于) db.mydb.user.find({age:{$gte:31}})
≠$ne (不等于) db.mydb.user.find({name:{$ne:”test3”}})
模糊查询 /^…/ db.mydb.user.find({name:/^test/})
$OR和$not查询
$in 用来查询一个键的多个值 相对的是$nin
db.mydb.user.find({users:{$in:["dsm","pddd"]}})
db.mydb.user.find({users:{$nin:["dsm","pddd"]}})
$or:比$in更通用一些,是用来查询多个键的任意给定键值,其值是一个数组形式
db.mydb.user.find({$or:[{users:"dsm"},{name:"aaa"}]})
db.mydb.user.find({$or:[{users:{$in:["dsm","pddd"]}},{name:"aaa"}]})
$not是元条件句,就是可以用在任意的其他查询条件上。
$not:是用来查询与特定模式不相符的文档。
db.mydb.user.find({age:{$not:{$mod:[5,0]}}})
$not 与正值表达是联合使用的时候极为有用。
条件句规则
在查询中,类似$lt的键处在内层文档,如修改器$inc则处在外层文档。
如:{age:{$lt:30}} 与 {$inc:{age:1}}
而且一个键可以含有多个条件,但不可以含有多个修改器:
{age:{$lt:30,$gt:20}} √
{$inc:{age:1},$set:{age:40}} × 不能在同一行修改同一个对age的修改
数组查询
$all
对于数组查询中,多个元素的匹配时,需要用到关键键$all. 如下:
db.mydb.insert({shop:”B”,fruit:[“apple”,” peach”,”orange”],size:3})
db.mydb.insert({shop:”C”,fruit:[“mango”,”banana”,” apple”],size:3})
查询既有”apple”又有”banana”的文档:
db.mydb.find({fruit:{$all:[“apple”,”banana”]}})
注:$all 查询不考虑键值的先后顺序问题。
精确匹配
使用[ ]精确匹配数组元素,既要考虑键值的个数,也要考虑元素位置:
db.mydb.find({fruit:[“apple”,”banana”,”orange”]})
下标查询
Key: 代表键。
Index:代表数组下标。
数组键值下标是从0开始。如下:
db.mydb.find({“fruit.1”:“banana”})
查询数组中第二的元素为“banana”的文档。
$size
在数组查询中,$size的查询意义非凡,顾名思义,该键就是查询指定长度的数组。
如:db.mydb.find({fruit:{$size:3}})
但是很不幸的是,它不可以与其他查询子句组合使用(”$lt“、”$gt“);
如:db.mydb.find({fruit:{$size:{$gt:3}}}) 是非法的。
但是这种查询可以通过在文档中增加一个size键来实现。
如:db.mydb.update({shop:”A”},{“$push”:{fruit:“Watermelon”},$inc:{size:1}})
db.mydb.find({size:{“$lt”:4}})
$slice
$slice 是用于子集查询,其返回值是一个子集合。如:
db.mydb.find({shop:”A”},{fruit:{$slice:2}})
上面查询表示查询数组的前2个子集。
也可以返回后2个子集:
db.mydb.find({shop:”A”},{fruit:{$slice:-2}})
它也可以接受偏移量[m,n]:
db.mydb.find({shop:”A”},{fruit:{$slice:[1,2]}})
表示跳过第一个,查询2个子集。
内嵌文档查询
单个内嵌文档
db.mydb.km.find({“person.name”:”zhangsan”,”person.age”:25})
这种点的查询方式就是区别于其他普通文档的主要特点,而且为什么对于键名不能使用.这个符号做出了最好的诠释。
注:键值匹配查询,对于内嵌文档值多个或者顺序未知的情况下,是有效的。
复杂内嵌文档
$where查询
需求:查询apple和banana值相等的文档。(就是如何匹配键值相等?)
普通查询是满足不了的。Mongo也没有提供这样的$关键键。只能通过$where表达式来执行:
db.mydb.fruit.find({“$where”:function(){
if(this.apple==this.banana) return true;
return false;
}})
等价db.mydb.fruit.find({“$where”:”this.apple==this.banana”})
$where 查询其返回值是表达式返回reture的当前文档。
优点:就是由于它可以使用javascript表达式,可想而知,查询非常灵活、多样。
缺点:使用$where查询,比起普通的查询速度要慢的多。因为其查询原理是将被查询文档转化成javacript对象,然后通过$where表达式去比较,而且还不能使用索引。所以不到万不得已尽量避免使用$where查询。
limit、skip和sort 使用
limit
limit 是限制返回结果集的数量,在find函数后使用该函数。
limit(number): 返回匹配number个文档。这里的number是上限,不是下限。
例如:
db.mydb.fruit.find().limit(3) // 返回匹配到的3个文档
如果结果少于3个,则全部返回。
skip
skip 类似于limit,区别在于:
skip(number): 略过匹配到的前number个文档,返回剩下的文档。
例如:
db.mydb.fruit.find().skip(3) //略过匹配到的前3个文档,返回余下的所有文档。
如果结果少于3个,则返回空。
sort
sort 是排序函数,它的参数一个文档对象(键、值),键是集合中的键,值是1或-1;
1 代表升序;-1 代表降序。
例如:
db.mydb.fruit.find().sort({apple:1,banana:-1})
由于文档型数据库的数据类型是不规则的,但mongo预定义了数据类型的排序规则。如:{a:[1,2]} 、{a:true}
从小到大 :
1、最小值 7、二进制 13、最大值
2、null 8、对象ID
3、数字 9、布尔型
4、字符串 10、日期
5、文档 11、时间戳
6、数组 12、正则表达式
实际开发中,三个函数组合使用的情况比较多。
例如(分页:第一页):
db.mydb.fruit.find().limit(20).sort({apple:1})
第二页:
db.mydb.fruit.find().limit(20).skip(20).sort({apple:1})
随机抽取文档
方法一
在实际应用中,随机抽取一个或多个文档是非常常见的需求,在mongo中实现起来有点繁琐:
最简单的实现方法:
先计算出一个从0到记录总数之间的随机数,然后采用skip(随机数)方法 。
var total=db.mydb.fruit.count();
var random=Math.floor(Math.random()*total);
db.mydb.fruit.find().skip(random).limit(1);
方法二
当数据量很大时,skip操作会变的很慢,应该避免使用skip来跳过大量的数据 。
可以采用另一种查询方式:
为每一条记录增设random字段,并赋值为Math.random(),查询时采用$gte和$lte。
db.mydb.fruit.update({banana:1},{$set:{random: Math.random()}})
var rand=Math.random();
db.mydb.fruit.find({random:{$gte:rand}});
方法三
高大上:借助Mongodb对地理空间索引(geospatial indexes)的支持,从而可以在上一个方法的基础上来实现随机记录的获取。
创建地理空间索引
db.mydb.fruit.ensureIndex({random:”2d”})
db.mydb.fruit.update({banana:1},{random:[Math.random,0]})
db. mydb.fruit.find ({random: { $near: [Math.random(), 0] } })
null
在查询中,null有点奇怪,它不仅可以匹配键值为null的文档,而且还可以匹配“不存在”的键。
如果仅仅想要匹配键值为null的文档,既要判断该键值是否为null,还有通过“$exists”判断该键是否存在。
例如:
db.mydb.test.find({z:{$in:[null],$exist:true}})
正则表达式
正值表达式能更灵活有效的匹配字符串。MongoDB使用Perl兼容的正值表达式(PCRE)库来匹配正值表达式,PCRE所支持的正值表达式都能被MongoDB接受。正值表达式也可以匹配自身。
例如:
{name:/jack/} 全匹配
{name:/jack/i} 忽略大小写
{name:/jac?/i} 相似匹配
{name:/^ja/} 模糊查询
聚合查询
count
最简单的聚合查询就是count查询,它的查询方式跟find是一样的,但是返回值不一样。Find 返回值是文档集,count 返回值是文档数。
例如:
db.mydb.fruit.count() ->查询文档总数
db.mydb.fruit.count({banana:1}) ->查询限定文档总数
distinct
distinct 是键值去重。其返回值是一个数组。
用法:
db.mydb.fruit.distinct(“键”[,查询条件])
如:
db.mydb.fruit.distinct(“apple”)
db.mydb.fruit.distinct(“apple”,{banana:1})
group
在mongodb里面做group操作有点小复杂,利用group可以将集合中的记录按一个或多个键分组,然后可以聚合每个分组内的文档产生新的结果。
用法:db.mydb.fruit.group({‘key’:{},'$reduce':function(doc,prev){},'initial':{}})
key:分组的键;
initial:累加器初始值,分组之后,每一组的reduce函数会调用这个初始文档,并在组内传递累加器;
$reduce:分组处理函数,分组之后会对每组元素遍历处理,该函数两个参数,第一个参数是当前的文档对象和第二个参数是上一次function操作的累计对象;
结果返回值为文档数组:
[{分组键,累加器}]
例如:
db.mydb.fruit.db.group({key:{apple:1},$reduce:function(doc,pre){
pre.bananaArr.push(doc.banana)
},initial:{bananaArr:[]}})