一、聚合查询
聚合(aggregation ),可以定义一系列行为,按照顺序(pipeline)依次执行
先创建一组测试数据如下:
db.people.insertMany([{
name:"Tom",
age:12,
gender:"M",
score:72,
type:"A"
},
{
name:"Jeck",
age:23,
gender:"M",
score:81,
type:"B"
},
{
name:"Rose",
age:14,
gender:"F",
score:74,
type:"A"
},
{
name:"Sali",
age:24,
gender:"F",
score:69,
type:"B"
},
{
name:"Harry",
age:32,
gender:"F",
score:82,
type:"A"
}])
1. 匹配分组
查询类型(type)是A的人之中,男女(gender)的总分分别是多少,并按照总分排序。
db.people.aggregate([
// 匹配type是A的人
{$match:{type:"A"}},
// 按照gender分组,计算score的和,结果是生成新的集合,所以需要吧gender作为新集合的_id字段
{$group:{_id:"$gender",total:{$sum:"$score"}}},
// 按照total排序
{$sort:{total:1}}
])
结果如下:
> db.people.aggregate([
... {$match:{type:"A"}},
... {$group:{_id:"$gender",total:{$sum:"$score"}}},
... {$sort:{total:1}}
... ])
{ "_id" : "M", "total" : 72 }
{ "_id" : "F", "total" : 156 }
>
2. project操作符
project用于可用于指定哪些字段显示,生成新的字段,字段拼接,格式转换等
-
指定显示字段,生成新字段
查看type类型是A,仅显示name和age字段,生成一个新字段new_f包含name和gender,这里的new_f是一个数组,并不是拼接
db.people.aggregate([ {$match:{type:"A"}}, {$project:{name:1,age:1,new_f:["$name","$gender"]}} ])
-
字符转换,条件语句
$toUpper // 转换为大写 $concat // 字符串拼接 $toString // 转换为字符串 $cond // 条件语句,里面可以写if,then,else
例:
db.people.aggregate([ {$match:{type:"A"}}, {$project: { // name转换为大写 name:{$toUpper:"$name"}, // 不显示_id字段 _id:0, // 新的字段new,是name和age拼接起来,age需要转换为字符串后拼接 new:{ $concat:["$name"," age is ",{$toString: "$age"}] }, // 新的字段flag,score大于80则good,否则bad flag: { $cond: { if: {$gt:["$score",80]}, then: "good", else: "bad" } } }} ])
结果如下:
> db.people.aggregate([ ... {$match:{type:"A"}}, ... {$project: ... { ... name:{$toUpper:"$name"}, ... _id:0, ... new:{ ... $concat:["$name"," age is ",{$toString: "$age"}] ... }, ... flag: { ... $cond: { ... if: {$gt:["$score",80]}, ... then: "good", ... else: "bad" ... } ... } ... }} ... ]) { "name" : "TOM", "new" : "Tom age is 12", "flag" : "bad" } { "name" : "ROSE", "new" : "Rose age is 14", "flag" : "bad" } { "name" : "HARRY", "new" : "Harry age is 32", "flag" : "good" } >
-
数组操作
$size :获取数组长度 $slice :数组切片
// 获取hobby的长度 {$size: "$hobby_list"} // 从hobby_list的第一个元素开始,取三个 {$slice: ["$hobby_list",1,3]}
3. bucket操作符
bucket操作符用于区间分组
db.people.aggregate([
{
$bucket:{
// 按照score分组
groupBy:"$score",
// 分组的区间60-70,70-80,80-90,每个区间含头不含尾
boundaries: [60,70,80,90],
// 输出的内容
output: {
// 计算总数
total:{$sum:1},
// 计算分数的均值
avg:{$avg:"$score"}
}
}
}
])
4. skip和limit
与find行为一样,限制返回行数和跳过行
skip必须放在limit前
db.people.aggregate([
// 按照gender分组,计算平均分
{
$group:{
_id:"$gender",
avg_score:{
$avg: "$score"
}
}
},
// 按分数排序
{$sort:{avg_score:1}},、
// 跳过第一条,仅显示一条(实际就是显示第二条数据)
{$skip:1},
{$limit:1}
])
5. out操作符
out把聚合的结果存储在新的collection中
db.people.aggregate([
// 按照gender分组,计算平均分
{
$group:{
_id:"$gender",
avg_score:{
$avg: "$score"
}
}
},
// 结果存储在gender_col这个collection中
{
$out:"gender_col"
}
])
执行完成后后查询gender_col可以看到刚才统计的结果
> db.gender_col.find()
{ "_id" : "M", "avg_score" : 76.5 }
{ "_id" : "F", "avg_score" : 75 }
>
二、索引
1. 基本命令
// 创建索引
db.testcol.createIndex({a:1})
// 查看索引
db.testcol.getIndexes()
// 删除索引(括号中是索引名,可以通过getIndexes看到)
db.testcol.dropIndex("a_1")
2. 单一索引
可以创建正序或是倒叙索引
// 1正序索引,-1倒叙索引
db.testcol.createIndex({a:1})
3. 唯一索引
唯一索引插入重复值会报错
db.testcol.createIndex({a:1},{unique:true})
4. 设置索引过期
可以在索引上设置过期时间,自动删除过期的数据(删除的是数据而不是索引)
比如可以用来记录日志,自动清理过期的日志
超时索引必须设置在一个时间类型的字段上,设置在其他类型字段上不生效。
// 插入一个包含时间类型的字段
db.testcol.insertOne({a:10,dt:new Date()})
// 创建索引,设置10秒过期
db.testcol.createIndex({dt:1},{expireAfterSeconds:10})
注:过期时间是从索引建立开始计算,后插入数据是从数据插入开始计算,并不是以日期字段存储的时间开始计算
5. 联合索引
联合索引中最多放32个
db.testcol.createIndex({a:1,b:-1})
联合索引第一个索引为主索引,单独使用主索引可以走索引,单独使用非主索引不会使用该索引
6. 文本索引
插入些测试数据
db.testcol.insertMany([{
a:"hello world good night"
},
{
a:"hello room yes ok"
},
{
a:"good world hello cool"
}])
文本索引创建之前,不能使用文本搜索
// 查找文本中包含hello关键字的(没有建立文本索引,会报错)
db.testcol.find({$text:{$search:"hello"}})
创建文本索引
db.testcol.createIndex({a:"text"})
-
单关键字查找
查找文本中包含world关键字的
> db.testcol.find({$text:{$search:"world"}}) { "_id" : ObjectId("5e7d881052a8aaded835f104"), "a" : "good world hello cool" } { "_id" : ObjectId("5e7d881052a8aaded835f102"), "a" : "hello world good night" } >
-
多关键字查找
查找包含hello或者world关键字的
> db.testcol.find({$text:{$search:"hello world"}}) { "_id" : ObjectId("5e7d881052a8aaded835f104"), "a" : "good world hello cool" } { "_id" : ObjectId("5e7d881052a8aaded835f103"), "a" : "hello room yes ok" } { "_id" : ObjectId("5e7d881052a8aaded835f102"), "a" : "hello world good night" } >
-
查找词组
查找包含hello world这个词组的记录
> db.testcol.find({$text:{$search:"\"hello world\""}}) { "_id" : ObjectId("5e7d881052a8aaded835f102"), "a" : "hello world good night" } >
-
大小写敏感
默认查找大小写不敏感
// 默认不区分大小写,可以查到3条记录 > db.testcol.find({$text:{$search:"Hello"}}) { "_id" : ObjectId("5e7d881052a8aaded835f104"), "a" : "good world hello cool" } { "_id" : ObjectId("5e7d881052a8aaded835f103"), "a" : "hello room yes ok" } { "_id" : ObjectId("5e7d881052a8aaded835f102"), "a" : "hello world good night" } // 大小写敏感,没有匹配的记录 > db.testcol.find({$text:{$search:"Hello",$caseSensitive:true}}) >
7. 后台创建索引
对数据量较大的collection创建index可能需要较长时间,测试前台shell无法执行其他操作
可以把创建index的操作放在后台
db.testcol.createIndex({a:1},{background:true})