MongoDB學習筆記系列:(六) 聚合操作和進階指南

一、聚合
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!" }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章