全文檢索
什麼是?
使用正則表達式模糊檢索文本內容,對於大段文本來說,效率很低,而且無法理解語義
這個時候可以使用全文檢索,可以快速進行文本檢索,且內置多種語言分詞機制,可理解語義,
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返回的結果對象: