1.MongDB核心概念
mongodb是最像关系型数据库的非关系数据库,其中很多思想和关系数据库很像,在mongodb中有几个核心概念:文档、集合、数据库如下:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
下面是数据库 , 集合 , 文档的包含关系图:
下面是关系型数据库表记录和mongdodb的文档对应关系图
1.1.数据库database
一个mongodb中可以建立多个数据库,默认数据库为"db",数据库存储在data目录中,不同的数据库也放置在不同的文件中,数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串。
- 不能是空字符串("")。
- 不得含有' '(空格)、.、$、/、\和\0 (空字符)。
- 应全部小写。
- 最多64字节。
MongoDB默认保留了几个数据库:
- admin : admin数据库则主要存储MongoDB的用户、角色等信息。
- local : local数据库主要存储副本集的元数据,它只会在本地存储数据
- config : 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
1.2.集合collections
一个集合包含一堆 MongoDB 文档,如果把Mysql中的一行行数据当做一个个文档,一张张表格就相当于是MongoDB中的集合
集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。 合法的集合名:
- 集合名不能是空字符串""。
- 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
- 集合名不能以"system."开头,这是为系统集合保留的前缀。
- 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。
1.3.文档document
通常一个对象可以映射成一个文档
,文档是一组键值(key-value)对(即 BSON,类似于JSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
需要注意的是:
- 文档中的键/值对是有序的。
- 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
- MongoDB区分类型和大小写。
- MongoDB的文档不能有重复的键。
- 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
文档键命名规范:
- 键不能含有\0 (空字符)。这个字符用来表示键的结尾。
- .和$有特别的意义,只有在特定环境下才能使用。
- 以下划线"_"开头的键是保留的(不是严格要求的)。
1.4.MongoDB 数据类型
下表为MongoDB中常用的几种数据类型。
数据类型 | 描述 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
Boolean | 布尔值。用于存储布尔值(真/假)。 |
Double | 双精度浮点值。用于存储浮点值。 |
Min/Max keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。 |
Array | 用于将数组或列表或多个值存储为一个键。 |
Timestamp | 时间戳。记录文档修改或添加的具体时间。 |
Object | 用于内嵌文档。 |
Null | 用于创建空值。 |
Symbol | 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。 |
Date | 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。 |
Object ID | 对象 ID。用于创建文档的 ID。 |
Binary Data | 二进制数据。用于存储二进制数据。 |
Code | 代码类型。用于在文档中存储 JavaScript 代码。 |
Regular expression | 正则表达式类型。用于存储正则表达式。 |
2.数据库操作
一个mongodb中可以建立多个数据库。MongoDB的默认数据库为"db",该数据库存储在data目录中。MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。
2.1.创建数据库
语法 use DATABASE_NAME
,如果数据库不存在,则创建数据库,否则切换到指定数据库。
实例:使用cmd ,输入mongo 进入mongo客户端终端,以下实例我们创建了数据库 hrm
> C:\Users\whale>mongo
> use hrm
switched to db hrm
2.2.显示所有数据库
如果你想查看所有数据库,可以使用 show dbs
命令:
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
可以看到,我们刚创建的数据库 hrm 并不在数据库的列表中, 要显示它,我们需要向 hrm 数据库插入一些数据。
使用 : db.数据库名.insert(BSON)
插入数据:
> db.hrm.insert({"name":"mongdb"})
WriteResult({ "nInserted" : 1 })
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
hrm 0.000GB
MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。
2.3.删除数据库
需要使用use 数据库
切换到数据库之后,执行db.dropDatabase()
> use hrm //切换到hrm数据库
switched to db hrm
> db.dropDatabase() //删除数据库
{ "dropped" : "hrm", "ok" : 1 }
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
>
3.集合(表)的操作
3.1.显示集合
如果要查看已有集合,可以使用 show collections
或 show tables
命令:
> use 数据库
> show tables
3.2.创建新集合
MongoDB 中使用 db.createCollection(name,options)
方法来创建集合。
db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } )
参数说明:
- name: 要创建的集合名称
- options: 可选参数, 指定有关内存大小及索引的选项
options 可以是如下参数:
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。 |
autoIndexId | 布尔 | 3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。 |
size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量。 |
在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。
实例:在 test 数据库中创建 hrm 集合:
> use hrm
switched to db hrm
> db.createCollection("user")
{ "ok" : 1 }
创建固定集合 mycol,整个集合空间大小 6142800 B, 文档最大个数为 10000 个。
> db.createCollection("mycol",{capped:true,autoIndexId:true,size:6142800,max:10000})
{
"ok" : 0,
"errmsg" : "Collection already exists. NS: hrm.mycol",
"code" : 48,
"codeName" : "NamespaceExists"
}
>
在 MongoDB 中,你不需要创建集合,当你插入一些文档时,MongoDB 会自动创建集合。
> db.mycol2.insert({"name" : "mongdb"})
> show collections
mycol2
...
3.3.删除集合
删除集合使用db.collection.drop()
,如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。
> use hrm
switched to db hrm
> show tables
mycol
role
user
> db.user.drop()
true
4.文档操作
4.1.插入文档
MongoDB 使用 db.collection.insert
,db.collection.insertOne
或 db.collection.save
方法向集合中插入文档,语法`
db.COLLECTION_NAME.insert(document)
或
db.COLLECTION_NAME.save(document)
- save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。
- insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
在 3.2 版本后还有以下几种语法可用于插入文档:
- db.collection.insertOne():向指定集合中插入一条文档数据
- db.collection.insertMany():向指定集合中插入多条文档数据
db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:
db.collection.insertOne(
<document>,
{
writeConcern: <document>
}
)
db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
参数说明:
- document:要写入的文档。
- writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
- ordered:指定是否按顺序写入,默认 true,按顺序写入。
实例:给user集合插入document
> var document = db.user.insertOne({"a": 3}) //给user集合插入数据,并保持结果到document变量
> document //打印document变量
{
"acknowledged" : true,
"insertedId" : ObjectId("605d8a844d99251c4e70bd60")
}
>db.user.find(); //查找user集合中的document
注意:如果没指定 “_id”属性,就会自动生成一个随机ID。
实例:给 user 集合批量插入数据
> var res = db.user.insertMany([{"b": 3}, {'c': 4}])
> res
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("605d8aec4d99251c4e70bd63"),
ObjectId("605d8aec4d99251c4e70bd64")
]
}
>
4.2.更新文档
使用 db.collection.update
方法更新已存在的文档。语法格式如下:
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
参数说明:
- query : update的查询条件,类似sql update查询内where后面的。
- update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
- upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
- multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
- writeConcern :可选,抛出异常的级别。
实例:修改指定文档
> db.user.insertOne({username:"zs",id:"1",age:18}); //添加一个文档
{
"acknowledged" : true,
"insertedId" : ObjectId("605d927e4d99251c4e70bd67")
}
> db.user.update({username:"zs"},{$set:{title:"ls"}}); //匹配username为 zs的文档,把username修改为ls
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find().pretty(); //查找user下的所有文档,pretty是进行格式化
{
"_id" : ObjectId("605d927e4d99251c4e70bd67"),
"username" : "zs",
"id" : "1",
"age" : 18,
"title" : "ls"
}
解释:db.user.update({username:"zs"},{$set:{title:"ls"}});
- {username:"zs"} :为第一个参数,对应query,满足该条件才会修改
- {$set:{title:"ls"}} :为第二个参数,把 title设置为 ls , 默认只会修改第一条
如果要修改所有文档把multi改为true
db.user.update({username:"zs"},{$set:{title:"ls"}} , false, true);
- 第三个参数fase:对应upsert,意思是不存在就插入数据
- 第四个参数true:对应multi,修改所有username为zs的文档把title修改为ls
注意:update只能修改文档中已经存在的属性
另外我们也可以使用 db.集合.save来更新文档,如:
> db.user.save({
"_id" : ObjectId("605d927e4d99251c4e70bd67"),
"username" : "ww", //username做了值的修改
"age" : 18,
"title" : "ls"
}); //注意:这里我少写了一个id字段
> db.user.find().pretty();
{
"_id" : ObjectId("605d927e4d99251c4e70bd67"),
"username" : "ww",
"age" : 18,
"title" : "ls"
}
这里的username确实被更新了,对于id字段由于我save的时候没有指定,所以最终就没有id字段了,也就是说这种更新相当于是把之前的文档干掉,保存新的文档进去。
4.3.删除文档
MongoDB中 remove() 函数是用来移除集合中的数据。格式如(2.6 版本以后的):
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
参数说明:
- query :(可选)删除的文档的条件。
- justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
- writeConcern :(可选)抛出异常的级别。
实例:插入3条username:zs的数据,然后再根据username:zs来删除
> db.user.insertOne({username:"zs",id:"1",age:18});
{
"acknowledged" : true,
"insertedId" : ObjectId("605d97224d99251c4e70bd68")
}
> db.user.insertOne({username:"zs",id:"2",age:19});
{
"acknowledged" : true,
"insertedId" : ObjectId("605d97224d99251c4e70bd69")
}
> db.user.insertOne({username:"zs",id:"3",age:20});
{
"acknowledged" : true,
"insertedId" : ObjectId("605d97234d99251c4e70bd6a")
}
> db.user.find(); //现在有3个文档,username都是zs
{ "_id" : ObjectId("605d97224d99251c4e70bd68"), "username" : "zs", "id" : "1", "age" : 18 }
{ "_id" : ObjectId("605d97224d99251c4e70bd69"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d97234d99251c4e70bd6a"), "username" : "zs", "id" : "3", "age" : 20 }
> db.user.remove({username:"zs"},1); //删除1条,username为zs的文档
> db.user.find().pretty(); //查找user集合中的文档 ,只剩下2个了
{ "_id" : ObjectId("605d97224d99251c4e70bd69"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d97234d99251c4e70bd6a"), "username" : "zs", "id" : "3", "age" : 20 }
> db.user.remove({username:"zs"}); //这一次并没有指定justOne为1,会删除所有
WriteResult({ "nRemoved" : 2 })
> db.user.find(); //再执行find,发现所有的username为zs的都删除了
如果想删除集合中的所有数据可以使用如下命令:
db.user.remove({})
4.4.查询文档
基本查询语法
MongoDB 查询数据的语法格式如下:
db.collection.find(query, projection)
- query :可选,使用查询操作符指定查询条件
- projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:
> db.collection.find().pretty()
pretty() 方法以格式化的方式来显示所有文档
> db.user.find();
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18 }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
> db.user.find().pretty();
{
"_id" : ObjectId("605d9a0b4d99251c4e70bd6d"),
"username" : "zs",
"id" : "1",
"age" : 18
}
{
"_id" : ObjectId("605d9a0b4d99251c4e70bd6e"),
"username" : "zs",
"id" : "2",
"age" : 19
}
{
"_id" : ObjectId("605d9a0c4d99251c4e70bd6f"),
"username" : "zs",
"id" : "3",
"age" : 20
}
>
除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。
> db.user.findOne();
{ "_id" : ObjectId("605d999c4d99251c4e70bd6b"), "a" : 3 }
在查找文档的过程中我们通常会增加查询条件,通过下表可以更好的理解 MongoDB 的条件语句查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 | {<key>:<value> } |
db.col.find({"by":"mongdb"}).pretty() |
where by = 'mongdb' |
小于 | {<key>:{$lt:}}` | `db.col.find({"likes":{$lt:50}}).pretty() |
where likes < 50 |
|
小于或等于 | {<key>:{$lte:}}` | `db.col.find({"likes":{$lte:50}}).pretty() |
where likes <= 50 |
|
大于 | {<key>:{$gt:}}` | `db.col.find({"likes":{$gt:50}}).pretty() |
where likes > 50 |
|
大于或等于 | {<key>:{$gte:}}` | `db.col.find({"likes":{$gte:50}}).pretty() |
where likes >= 50 |
|
不等于 | {<key>:{$ne:}}` | `db.col.find({"likes":{$ne:50}}).pretty() |
where likes != 50 |
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。语法格式如下:
db.col.find({key1:value1, key2:value2}).pretty()
实例:查找username为zs,id为1的文档
> db.user.find({username:"zs",id:"2"});
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
以上实例中类似于 WHERE 语句:where username='zs' AND id='2'
去重distinct
语法 db.user.distinct("属性")
> db.usertable.distinct("username")
[ "zs" ]
or 和 and 语法
MongoDB 支持 ,or ,and ,not 运算,分别对应关键字 $or(或者),$and(并且) , $not(取反) ,语法格式如下:
>db.col.find(
{
$or: [
{key1: value1}, {key2:value2}
]
}
).pretty()
实例:查找username为zs 或者 id 为2的文档
> db.user.find({$or:[ {username:"zs"},{"id":2} ]});
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18 }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
>
实例:查找username为zs and id为2的文档
> db.user.find({"username":"zs","id":2});
上面语法等同于
> db.user.find({$and:[ {username:"zs"},{"id":2} ]});
以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: 'where age >17 AND (id = 1 OR id = 2)'
> db.user.find({age:{$gt:17},$or:[{id:"1"},{id:"2"}]})
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18, "title" : "ls" }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
取模运算
MongoDB支持取模运算,类似:where id % 2 = 1
db.user.find({"_id":{"$mod":[2,1]}})
in和nin
成员运算符 in和not in ,在Mongdb中通过$in , $nin
实现 ,类似于 where age in (1,2,3)
> db.user.find({"age":{"$in":[1,2,3]}})
条件操作符号
MongoDB中条件操作符有:
- (!=) 不等于 - $ne
- (>) 大于 - $gt
- (<) 小于 - $lt
- (>=) 大于等于 - $gte
- (<= ) 小于等于 - $lte
如果你想获取 "user" 集合中 "age" 大于 18 的数据,你可以使用以下命令:
db.user.find({age : {$gt : 18}})
如果你想获取"user"集合中 "a"ge 大于17,小于 19 的数据,你可以使用以下命令:
db.user.find({age : {$lt :19, $gt : 17}})
正则查询
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。以下命令使用正则表达式查找包含 zs 字符串的文档:
>db.user.find({username:{$regex:"zs"}})
也可以使用如下命令
>db.user.find({username:/zs/})
数组查询
当文档的某个field为数组时,可以使用数组查询 , 如下:查询hobbles中包含 dancing的文档
> db.user.find({"hobbles":"dancing"})
查询hobbles中既包含dancing,又包含tea的文档
> db.user.find({"hobbles":{"$all":["dancing","tea"]}})
{ "_id" : 2, "username" : "ls", "hobbles" : [ "dancing", "tea" ] }
{ "_id" : 3, "username" : "ww", "hobbles" : [ "dancing", "tea" ] }
查询子文档 stu.name为xoxo的文档
> db.usertable.find({"stu.name":"xoxo"})
limit 和 skip
如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数 ,limit()方法基本语法如下所示:
> db.COLLECTION_NAME.find().limit(NUMBER)
实例:查询2条user集合中的文档
> db.user.find().limit(2);
{ "_id" : ObjectId("605d999c4d99251c4e70bd6b"), "a" : 3 }
{ "_id" : ObjectId("605d99a24d99251c4e70bd6c"), "a" : 4 }
我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。
实例:跳过3条数据,查询2条数据
> db.user.find();
{ "_id" : ObjectId("605d999c4d99251c4e70bd6b"), "a" : 3 }
{ "_id" : ObjectId("605d99a24d99251c4e70bd6c"), "a" : 4 }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18, "title" : "ls" }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
{ "_id" : ObjectId("605ea3122e299dc86bcfd1a4"), "id" : 1, "username" : "zs" }
> db.user.find().limit(2).skip(3);
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
该效果类似于mysql的: limit 3 , 2
sort排序
在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。
sort()方法基本语法如下所示:
> db.COLLECTION_NAME.find().sort({KEY:1})
实例:对user按照age排序
> db.user.find();
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18, "title" : "ls" }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
> db.user.find().sort({age:-1}) //倒着排序
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18, "title" : "ls" }
统计查询
统计id大于3的条数
db.user.count({"_id":{"$gt":3}})
等同于
db.user.find({"_id":{"$gt":3}}).count()
聚合查询
聚合操作主要用于处理数据并返回计算结果。聚合操作将来自多个文档的值组合在一起,按条件分组后,再进行一系列操作(如求和、平均值、最大值、最小值)以返回单个结果。
MongoDB 中的聚合操作语法如下:
db.collection.aggregate([
{ $match : {< query >},}
{ $group: {< fieldl >: < field2 >} }
])
Query 设置统计查询条件,类似于 SQL 的 where,field1 为分类字段,要求使用 _id 名表示分类字段,field2 为包含各种统计操作符的数字型字段,如 $sum、$avg、$min 等。这个语法看起来比较难以理解,下面给出一个示例进行对照:
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
第一部分:$match
阶段按status
字段过滤文档,并将status
等于"A"
的文档传递到下一阶段。-> 如同 where state = "A"
第二部分:$group
阶段按cust_id
字段将文档分组,以计算每个唯一值cust_id
的金额总和。 -> 如同 select sum(amount) as total from orders group by cust_id 。
实例:根据username分组后统计数量
> db.user.find();
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6d"), "username" : "zs", "id" : "1", "age" : 18, "title" : "ls" }
{ "_id" : ObjectId("605d9a0b4d99251c4e70bd6e"), "username" : "zs", "id" : "2", "age" : 19 }
{ "_id" : ObjectId("605d9a0c4d99251c4e70bd6f"), "username" : "zs", "id" : "3", "age" : 20 }
{ "_id" : ObjectId("605f48a720a5d5fab49e1c9e"), "username" : "ls", "age" : 19 }
> db.user.aggregate([{$group:{ _id:"$username",age:{$sum:1}}}]);
{ "_id" : "ls", "age" : 1 }
{ "_id" : "zs", "age" : 3 }
相当于Mysql中的 : select username as _id, count(*) from user group by username
实例:查询age大于17 , 并按照age分组,分组之后求总条数和求总额
> db.user.find()
{ "_id" : 1, "username" : "zs", "age" : 18, "amount" : 100 }
{ "_id" : 2, "username" : "ls", "age" : 18, "amount" : 100 }
{ "_id" : 3, "username" : "ww", "age" : 19, "amount" : 200 }
{ "_id" : 4, "username" : "zy", "age" : 19, "amount" : 300 }
> db.user.aggregate([{$match:{"age":{$gt:17}}},{$group:{ _id:"$age",count:{$sum:1},amount:{$sum:"$amount"}}}])
{ "_id" : 18, "count" : 2, "amount" : 200 }
{ "_id" : 19, "count" : 2, "amount" : 500 }
上面实例类似于:select count(*) as count, sum(amount) as amount from user where age > 17 group by age
下表展示了一些聚合的表达式:
表达式 | 描述 | 实例 |
---|---|---|
$sum | 计算总和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) | ||
$avg | 计算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) | ||
$min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) | ||
$max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) | ||
$push | 在结果文档中插入值到一个数组中。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) | ||
$addToSet | 在结果文档中插入值到一个数组中,但不创建副本。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) | ||
$first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) | ||
$last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) |
综合练习
数据准备
db.emp.save({"_id":4,"username":"zy","age":19,"amount":300,"dept":"dept1"})
db.emp.save({"_id":1,"username":"zs","age":19,"amount":300,"dept":"dept2"})
db.emp.save({"_id":2,"username":"ls","age":20,"amount":200,"dept":"dept3"})
db.emp.save({"_id":3,"username":"ww","age":20,"amount":200,"dept":"dept3"}) db.emp.save({"_id":5,"username":"zyt","age":22,"amount":500,"dept":"dept4"})
案例:求每个部门的总人数
> db.emp.aggregate([{$group:{"_id":"$dept",usernum:{"$sum":1}}}])
{ "_id" : "dept2", "usernum" : 1 }
{ "_id" : "dept3", "usernum" : 2 }
{ "_id" : "dept4", "usernum" : 1 }
{ "_id" : "dept1", "usernum" : 1 }
案例:求每个部门平均工资
> db.emp.aggregate([{$group:{"_id":"$dept",amount:{"$avg":"$amount"}}}])
{ "_id" : "dept2", "amount" : 300 }
{ "_id" : "dept3", "amount" : 200 }
{ "_id" : "dept4", "amount" : 500 }
{ "_id" : "dept1", "amount" : 300 }
案例:查询工资200以上员工,每个部门的总工资
> db.emp.aggregate([{$match:{"amount":{"$gt":200}}},{$group:{"_id":"$dept",total:{"$sum":"$amount"}}}])
{ "_id" : "dept2", "total" : 300 }
{ "_id" : "dept4", "total" : 500 }
{ "_id" : "dept1", "total" : 300 }
案例:查询每个部门的最高工资 和 最低工资
> db.emp.aggregate([{$group:{"_id":"$dept",maxamount:{"$max":"$amount"},minamount:{"$min":"$amount"}}}])
{ "_id" : "dept3", "maxamount" : 200, "minamount" : 200 }
{ "_id" : "dept2", "maxamount" : 300, "minamount" : 300 }
{ "_id" : "dept1", "maxamount" : 300, "minamount" : 300 }
{ "_id" : "dept4", "maxamount" : 500, "minamount" : 500 }
5.索引的使用
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构 , 在Mysql中索引使用的是B+Tree作为存储结构, 在Mongodb中使用的是b-tree结构。
当搜索某个内容的时候,会先在索引中进行快速搜索,然后定位到数据库中的数据发回给用户。
5.1.索引分类
单字段索引
: 如果使用单个字段创建的索引就是单字段索引(single field index) , 索引支持升续或者降续排序。
复合索引
:多个字段建立的索引就是复合索引(compound index),复合索引中的字段顺序具有很重要的意义,比如按照{uid:1,score:-1} 创建索引,会先按照uid正序排序,相同的uid再按照score倒叙排序。这对索引的查询有很大的影响。
其他索引
: 地理空间索引:用得少,文本索引:推荐使用ES搜索引擎,哈希索引:分片中用到
5.2.查看索引
MongoDB使用 db.collection.getIndexes() 方法来查看索引。 默认情况下mongodb会以id创建索引
> db.user.getIndexes()
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]
- v : 版本号
- key : 索引的列 , 1 正序索引
- name : 索引的名字
5.3.创建索引
MongoDB使用 createIndex() 方法来创建索引。
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。
createIndex()方法基本语法格式如下所示:
>db.collection.createIndex(keys, options)
语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
实例:给user集合的username创建索引,按升续排序索引
> db.user.createIndex({id:1});
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
createIndex( keys, options ) , options 接收可选参数,可选参数列表如下:
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
实例:使用id列,创建一个唯一的正排索引 , 需要加上 options 选项unique:true
db.user.createIndex({id:1},{unique:true})
实例:创建复合索引 , 给多个字段创建索引
>db.user.createIndex({"id":1,"age":-1})
5.4.删除索引
MongoDB使用 db.collection.dropIndex({}) 方法来删除索引。
实例:删除user集合中, userId为升续的索引
> db.user.dropIndex({"userId":1})
{ "nIndexesWas" : 3, "ok" : 1 }
注意:这里的userId是字段 , 1 指的是升续 , 除了可以通过 字段名去删除,还可以通过索引的name名称去删除。
5.5.索引执行计划
和Mysql一样,Mongodb提供了执行计划查询,通过它我们可以分析出查询命令是否用到索引,从而进行优化。语法如下
db.collection.find(query.options).explain(options)
实例:分析一个find命令的执行计划
> db.user.find({"username":"zs"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "tempdb.user",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "zs"
}
},
"queryHash" : "379E82C5",
"planCacheKey" : "379E82C5",
"winningPlan" : {
"stage" : "COLLSCAN", #全集合扫描,没有走索引
"filter" : {
"username" : {
"$eq" : "zs"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "LAPTOP-20VLGCRC",
"port" : 27017,
"version" : "4.4.4",
"gitVersion" : "8db30a63db1a9d84bdcad0c83369623f708e0397"
},
"ok" : 1
}
创建索引再分析
> db.user.createIndex({"username":1})
> db.user.find({"username":"zs"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "tempdb.user",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "zs"
}
},
"queryHash" : "379E82C5",
"planCacheKey" : "965E0A67",
"winningPlan" : {
"stage" : "FETCH", #fetch代表使用了索引
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"username" : 1
},
"indexName" : "username_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"username" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"username" : [
"[\"zs\", \"zs\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "LAPTOP-20VLGCRC",
"port" : 27017,
"version" : "4.4.4",
"gitVersion" : "8db30a63db1a9d84bdcad0c83369623f708e0397"
},
"ok" : 1
}
解释: 注意看 "stage" : "FETCH", 代表用到了索引的
知识扩展:
涵盖索引 : 在查询内容的时候会先去索引中查询,查到后根据ID取相应的数据 ,如果查询的内容已经包含在索引中就不需要再一次取回数据,这就叫涵盖索引。比如以 : name和age 创建了复合索引 , 而查询的结果也只需要这2个字段,那么就直接从索引中返回数据,性能最高。