前端mongo聚合筆記

MongoDB 是一個基於分佈式文件存儲的數據庫。由 C++ 語言編寫。旨在爲 WEB 應用提供可擴展的高性能數據存儲解決方案。javascript

1.前言

自從先後端分離後就一直有爭辯,先後端分離的界限在哪裏,前端僅僅是寫頁面嗎?須要學習服務器數據庫等後端的知識嗎?前端

我認爲是很是有必要的,假如就籠統以瀏覽器和服務器區分先後端,如何解釋瀏覽器的 indexedDB 的存在。存在就是合理的,瞭解越多能夠走得越遠。java

公司最近有 mongo 聚合的需求,我瞬間有了極大的興趣,這篇文章是 mongo 的學習和解決問題筆記。jquery

2.環境

環境安裝與本篇文章的關係不大,但提供幾個渠道:mongodb

  1. 跟着 mongo 官方文檔來安裝,本地啓動調試;
  2. mongo 客戶端,Robo 3T很好用哦;
  3. mongo 官方的命令行小窗口免安裝直接使用,學習推薦)。

3.介紹

在城市中有幾所學校(db),咱們來查看下城市中的學校都有哪些數據庫

3-1

而後須要指定學校,做用是能夠操做該學校(mongo默認指向test後端

3-2

4.插入

學校(test)到了期末考試,成績以下:數組

姓名 性別 語文 數學 英語 參加考試時間
小一 男生 90 90 90 2019年6月10號
小二 女生 80 80 80 2019年6月10號
小三 女生 70 70 70 2019年6月10號
小四 女生 60 60 60 2019年6月12號

成績出來後老師把分數錄入成績系統。瀏覽器

// 插入的方法:insertOne插入一條數據
db.student.insertOne({ 
  name: "小一", 
  sex: '男生', 
  chinese: '90', 
  math: '90', 
  english: '90', 
  createdAt: new Date('2016-06-09T16:00:00Z')
})
複製代碼

4-1

注意:new Date('2016-06-09T16:00:00Z')是世界標準時間,mongo記錄的也是世界標準時間。中國在東八區,時間加8小時,換算過來恰好是2019年6月10號。服務器

老師以爲一個個添加太煩了,因此一次性添加:

// // 插入的方法:insertMany 插入一條數據
db.student.insertMany([
  { name: "小二", sex: '女生', chinese: '80', math: '80', english: '80', createdAt: new Date('2016-06-09T16:00:00Z')},
  { name: "小三", sex: '女生', chinese: '70', math: '70', english: '70', createdAt: new Date('2016-06-09T16:00:00Z')},
  { name: "小四", sex: '女生', chinese: '60', math: '60', english: '60', createdAt: new Date('2016-06-11T16:00:00Z')}
])
複製代碼

4-2

  1. 學校(db):數據庫,mongo能夠容納多個數據庫,默認使用test
  2. 學生集合(student):集合,能夠理解爲集合全部學生的班級;
  3. 學生信息(data):文檔,能夠理解爲單個學生信息。

5.修改

老師忽然發現,小三實際上是女裝大佬,老師被騙惱羞成怒,因而修改其性別。

// 更新方法:updateOne
db.student.updateOne(
  { name: '小三' },
  { $set: { sex: '女裝大佬' }}
)
複製代碼

5-1

其中 $set 是更新操做符,告訴 mongo 須要對符合的數據進行什麼操做。用法差很少,列舉部分以下(更多更新操做符):

  1. $currentDate:設置時間;
  2. $inc:按指定的數量增長字段的值;
  3. $min:僅當指定的值小於現有字段值時才更新字段;
  4. $max:僅當指定的值大於現有字段值時才更新字段;
  5. $mul:將字段的值乘以指定的量;
  6. $rename:重命名字段;
  7. $set:設置文檔中字段的值;
  8. $setOnInsert:若是更新致使文檔插入,則設置字段的值。對修改現有文檔的更新操做沒有影響;
  9. $unset:從文檔中刪除指定的字段。

6.查找

三個學生都努力學習,通過期末考試完後,小一自信能夠拿第一,因而小一去查本身的成績。

// 查詢方法:find
// pretty方法可讓數據更好看
db.student.find({name: '小一'}).pretty()
複製代碼

6-1

而後再查小二和小三的成績。他忽然發現小三是女裝大佬的事實,震驚!

// 查詢方法:find
db.student.find({$or: [{name: '小二'}, {name: '小三'}]}).pretty()
複製代碼

6-2

其中 $or 是查詢操做符,告訴 mongo 須要對符合的數據進行什麼操做。用法差很少,列舉部分以下(更多查詢操做符):

  1. $eq:匹配等於指定值的值;
  2. $gt:匹配大於指定值的值;
  3. $lt:匹配小於指定值的值;
  4. $and:列舉條件都匹配;
  5. $not:列舉條件都不匹配;
  6. $or:列舉條件匹配其中一個。

7.刪除

小一心裏糾結,最後把這事告訴小三。小三以爲無顏面對同窗和老師,決定退學。老師把小三的信息從教務系統刪除。

// 刪除方法:deleteOne
db.student.deleteOne({name: '小三'})
複製代碼

7-1

8.循環

期末老師作工做總結,發現成績錄入成字符串,應該是數字類型纔對,因此操做循環更改。

// 遍歷方法:forEach
db.student.find().forEach(function(doc) {
  doc.chinese = NumberInt(doc.chinese);
  doc.math = NumberInt(doc.math);
  doc.english = NumberInt(doc.english);
  db.student.save(doc);
})
複製代碼

8-1

數字類型:NumberInt強制轉化爲數字類型。

9.聚合

期末老師須要把班級的語文、數學、英語的平均分計算出來。

9.1 aggregate

// 聚合方法:aggregate
// 而$chinese、$math和$english只是表明文檔字段。
db.student.aggregate([
  {
    $group: {
      _id: null,
      sumChinese: { $sum: '$chinese' },
      avgChinese: { $avg: '$chinese' },
      sumMath: { $sum: '$math' },
      avgMath: { $avg: '$math' },
      sumEnglish: { $sum: '$english' },
      avgEnglish: { $avg: '$english' },
    }
  }
]).pretty()
複製代碼

9-1

$group是分組操做符,根據_id來進行分組,若是傳null就是不分組,選擇全部的數據。 其中 $sum$avg 是聚合操做符,告訴 mongo 須要對符合的數據進行什麼操做。用法差很少,列舉部分以下(更多聚合操做符):

  1. $sum:返回每一個組的總和(忽略非數字值);
  2. $avg:返回每一個組的平均值(忽略非數字值);
  3. $max:返回每一個組的最高表達式值;
  4. $min:返回每一個組的最低表達式值;
  5. $gte:若是給定的值大於等於,返回true,不然返回false;
  6. $lte:若是給定的值小於等於,返回true,不然返回false;
  7. $add:添加數字以返回總和,或添加數字和日期以返回新日期;
  8. $year:以數字(例如2014)返回日期的年份;
  9. $month:以1(1月)到12(12月)之間的數字返回日期的月份;
  10. $dayOfMonth:以1到31之間的數字返回日期的月中某天。

老師發現上面的計算方式是計算全部的學生的成績,而此次只須要計算2019年6月10號考試成績,修改以下:

db.student.aggregate([
  {
    $match: {
      createdAt: {
        $gte: new Date('2016-06-09T16:00:00Z'),
        $lte: new Date('2016-06-10T16:00:00Z')
      }
    },
  },
  {
    $project: {
      name: 1,
      sex: 1,
      chinese: 1,
      math: 1,
      english: 1,
      time: {$add: ['$createdAt', 8 * 60 * 60 * 1000]}
    }
  },
  {
    $group: {
      _id: {
        year: {'$year': '$time'},
        month: {'$month': '$time'},
        day: {'$dayOfMonth': '$time'},
      },
      sumChinese: { $sum: '$chinese' },
      avgChinese: { $avg: '$chinese' },
      sumMath: { $sum: '$math' },
      avgMath: { $avg: '$math' },
      sumEnglish: { $sum: '$english' },
      avgEnglish: { $avg: '$english' },
    }
  }
]).pretty()
複製代碼

9-2

time 字段用來顯示中國真實時間,它的值是 createedAt 的值再加8個小時。由於 mongo 默認存儲世界標準時間,中國在東八區,須要加8個小時才能正確顯示中國時間。

  1. $match :篩選操做符,顧名思義只有知足條件的數據才繼續傳遞下去;
  2. $project :修改返回數據結構的操做符,1表示顯示,不寫或者0則不顯示。(aggregate 聚合方法接受數組參數,由於有管道概念,簡單說前面命令執行完的結果做爲後面命令的參數,像jquery的點鏈接符。)

9.2 MapReduce

除了使用聚合方法,還可使用 MapReduce 方法來計算。

Map-Reduce 是一種計算模型,簡單的說就是將大批量的工做(數據)分解(MAP)執行,而後再將結果合併成最終結果(REDUCE)。MongoDB提供的Map-Reduce很是靈活,對於大規模數據分析也至關實用。

db.student.mapReduce(
  function(){
    emit(null, this);
  }, 
  function(key, values){
    var reducedVal = {
      sumChinese: 0,
      avgChinese: 0,
      sumMath: 0,
      avgMath: 0,
      sumEnglish: 0,
      avgEnglish: 0
    };
    
    for (var i = 0; i < values.length; i++) {
      reducedVal.sumChinese += values[i].chinese;
      reducedVal.sumMath += values[i].math;
      reducedVal.sumEnglish += values[i].english;
    }
    
    reducedVal.avgChinese = reducedVal.sumChinese / values.length;
    reducedVal.avgMath = reducedVal.sumMath / values.length;
    reducedVal.avgEnglish = reducedVal.sumEnglish / values.length;
    
    return reducedVal;
  }, 
  {
    query: {createdAt: {$gte: new Date('2016-06-09T16:00:00Z'), $lte: new Date('2016-06-10T16:00:00Z')}}, 
    out: 'sum'
  }
).find().pretty()
複製代碼

9-3
上面 mongo 語句的意思是:

  1. 根據 query 篩選出合適的數據(至關於聚合 aggregate$match ),交給 map 函數(mapReduce接受的第一個參數);
  2. map 函數調用 emit 函數遍歷全部篩選數據,傳遞的第一個參數表示分組,咱們不須要分組就傳遞 null,第二個參數是對應的數據,咱們把完整數據傳遞下去給給 reduce 函數(mapReduce接受的第二個參數);
  3. reduce 函數接收 map 傳遞的 keyvalues 值(就是對應上面的 emit(null, this)),咱們只須要按本身的需求遍歷組裝,返回組裝好的數據就能夠了。

10.總結

mongo 很好玩,本篇文章只是記錄功能開發中運用 mongo的一些場景,並且 mongo 的內容遠遠不止本篇文章寫的這麼一點點範圍,有興趣能夠查閱官方文檔

相關文章
相關標籤/搜索