經驗拾憶(純手工)=> MongoDB與PyMongo語法對比解析

閱讀須知

由於是對比書寫:
    M: 代表 Mongo原生語法
    P: 代表 PyMongo書寫方法
    
    後面提到:”同上“ 字眼:
        意思就是 Mongo 和 PyMongo 語句是一模一樣的, 一個字都不差,複製上去,可以直接運行
        (也許你很好奇,爲什麼 一個是Python語言裏的PyMongo,一個是Mongo)
        他們的語句爲什麼可以做到一模一樣 ??
        答:因爲 Mongo和Python都可以 給變量賦值, PyMongo的語法設計也是模仿Mongo的。
            所以:我巧妙的 把二者的變量設爲同一個,函數90%都一致, 所以整條語句就一模一樣了!
    
    主要語法區別:
        1. 函數命名
            Mongo  方法函數大都以 駝峯命名
            PyMongo方法函數大都以 _ 下劃線分割命名
        2. 函數參數
            Mongo :  基本都是 {} + [] 各組組合格式 
            PyMongo:同上, 但{}的 key需要使用字符串格式, 有些情況,還需要使用命名參數代替 {}
        3. 空值 與 Bool
            Mongo: null  true false
            PyMongo: None True False

        

前置安裝配置環境

  • 客戶端連接:

     pip install pymongo
     import pymongo
        
     M: Mongo
     P: cursor = pymongo.MongoClient('ip',port=27017) 
    
  • 選擇數據庫:

     M: use test
     P: db = cursor['test']       # 記住這個db,  下面複用這個參數
  • 選擇集合: (記住table變量名,下面就直接用他們了) 注意,注意,注意

     M: table = db.zhang                         
     P: table = db['zhang']  
    
     注:選擇庫,選擇集合的時候 注意事項:
     Mongo中:  xx.xx  用 . 的語法
     PyMongo中:也可以 用 xx.xx 這樣,  但是這樣用在PyCharm中沒有語法提示
     
     所以提倡     xx['xx']      用索引的方式使用
    
  • Mongo 與 PyMongo 返回結果的遊標比較

     Mongo中:
         大多數查詢等結果返回都是遊標對象
         如果不對遊標遍歷,那麼Mongo的遊標會默認爲你取出 前 20 個 值
         當然,你也可以索引取值
         關閉操作: .close()                   
     PyMongo中:
         同樣,大多數查詢等結果返回都是遊標對象(如果你學過ORM,可以理解遊標就像 ORM的查詢集)
         所以必須通過 list() 或 遍歷 或 索引 等操作才能真正取出值
         關閉操作: .close()  或者 用 Python 的 with 上下文協議

  • save()

    M: table.save({})    # 估計要廢棄了
    P: 將要被廢棄 用insert_one代替它
    
  • insert()

    M: table.insert()         # 包括上面兩種,可以一個 {},可以多個 [{},{}]
    P: PyMongo源碼明確說明,insert()語法將被廢棄,請用 insert_one({}) 和 insert_many([])代替
    
  • insert_one() 和 insert_many()

    M: 
       table.insertOne( {} )            # 駝峯
       table.insertMany([ {},{} ])      # 駝峯
    P:
       table.insert_one( {} )           # 下劃線
       table.insert_many([ {},{} ])     # 下劃線

  • remove()

    參數1:刪除查詢條件
    參數2:刪除選項
    M: table.remove({'name':'zhangsan'}, {'justOne': true})   # 我更喜歡用delete的
    P: PyMongo中,此方法將被廢棄。 將會被 delete_one() 和 delete_many() 代替
    
  • deleteOne() # 只刪除一條

    M: table.deleteOne({'name': 'lin3'})
    P: table.delete_one({'name': 'lin3'})    # 
  • deleteMany() # 刪除多條

    M: table.deleteMany({'name': 'lin3'})
    P: table.delete_many({'name': 'lin3'})
    
    注意:
        不知道這兩個函數是否讓你想起了前面講的  insertOne 和 insertMany,他們看起來很像,語法不同:
            insertMany([]) # 參數需要用   [] 包起來
            deleteMany({}) # 參數不需要
    注意2:
        table.deleteMany({})    # 空 {}, 代表刪除所有文檔 (慎行,慎行,慎行)
  • 刪除整個集合:

    table.drop()    # 刪除集合(連同 所有文檔, 連同 索引,全部刪除)
    

"""
    文檔修改,  注意: _id 不可修改
"""
  • 三種更新方法:

    1. update(將要廢棄,可跳過,直接看2,3點的方法)
       update({查詢條件},  {更新操作符} , {更新選項})
       
       M: table.update({'name': {'$regex':'li'}},{'$set':{'name':'lin2'}}, {multi: true})
       P: table.update({'name': {'$regex': 'li'}}, {'$set': {'name': 'lin3'}},multi=True)
       
       注意1: 第三個參數 multi如果不設置,默認只更新一條文檔,設置爲 true ,就會更新多條文檔
       注意2:
           Mongo寫法: {multi: true}        # Mongo 和往常一樣,採用json格式, true小寫
           Python寫法: multi = True        # python是採用命名參數來傳遞, True大寫
           
    2. updateOne(更新一條) 
           M: updateOne( {查詢條件},  {更新操作符} )   
           P: update_one
    3. updateMany(更新多條)
           M: updateMany( {查詢條件},  {更新操作符} )     其實參數是一模一樣的,只不過方法名區分
           P: update_many
           
           
     注: 這三個方法的參數 是基本一模一樣的
          所以下面講具體  {查詢條件},  {更新操作符} 時
          就統一用 update()來寫了
    
    
  • 普通更新操作符:

  • $set(更新)

    # 注:規則就是:"有則改之, 無則添加"
    M: table.update({'5':5},{'$set': {'lin': [5,6,7,8]} })
    P: 同上
    
    微擴展(關於內嵌數組):
        table.update({'5':5},{'$set': {'lin.0': '呵呵' })  # lin.0代表數組的第一個元素
        當數組的索引越界,這個時候就視爲數組的添加操作。 
        eg: 假定我們給 lin.10 一個值,那麼 中間空出的那麼多索引,會自動填充 null    
           
  • $unset(刪除)

    # 注:刪除的鍵對應的value可以隨便寫,寫啥都會刪除, 寫 '' 只是爲了語義明確(規範)
    M: table.update({'6':6}, {'$unset': {'6':''}})     # 把此條記錄的 '6' 字段刪除
    P: 同上
       
    微擴展(關於嵌套數組):
        table.update({'5':5}, {'$unset': {'lin.0':''}}) # lin.0同樣代表數組第一個元素
        注:數組的刪除 並不是真正的刪除, 而是把值 用 null 替換
           
  • $rename(改名,替換)

    M: table.update({'name':'lin'}, {'$rename':{'name':'nick'}})  # name變成了nick
    P: 同上
    微擴展(文檔嵌套):
        如果文檔是嵌套的 eg:   { a: {b:c} } 
            M: table.update({'lin':'lin'}, {'$rename': {'a.b':'d'}})
            P: 同上
            結果 => {"a" : {  }, "d" : "c" }
        解析:
            b   屬於 子文檔
            a.b 表示 通過父文檔的a 來取出 子文檔的b
            如果整體a.b被 rename爲 d,那麼 d會被安排到父文檔的層級裏,而a設爲空。
            舉個栗子:
                你有一個箱子,裏面 有一個 兒子級別 和 孫子級別 的箱子 (共3層)
                現在你把 孫子級別的箱子 單獨拿出來, 把整個箱子替換掉
                就是這種思想。。。自己體會吧   
                             
               (這種語法,好像Python列表的切片賦值。。形容可能不太恰當)
               
  • $inc:

    {$inc: { 'age': -2}}    # 減少兩歲,正數表示加法,負數表示減法,簡單,不舉例了
    特例:如果字段不存在,那麼,此字段會被添加, 並且值就是你設定的值(0+n=n)
  • $mul:

    {$mul: { 'age': 0.5}}   # 年齡除以2,整數表示乘法,小數表示除法,簡單,不舉例了
    特例:如果字段不存在,那麼,此字段會被添加, 並且值爲0 (0*n=0)                    
          
  • $min

    {$min: { 'age': 30}}    # 30比原有值小:就替換, 30比原有值大,則不做任何操作
  • $max

    {$max: { 'age': 30}}    # 30比原有值大:就替換, 30比原有值小,則不做任何操作
    特例:min和max特例相同,即如果字段不存在,那麼,此字段會被添加, 並且值就是你設定的值
    
  • 數組更新操作符:

    """
        單數組:   xx
        內嵌數組: xx.索引
    """
  • $addToSet(有序,無重複,尾部添加)

    原始數據: {'1':1}
       
    M: table.update({'1':1}, {'$addToSet':{'lin':[7,8]}})    
    P: 同上
    
    結果 => {"1": 1,"lin": [ [7, 8 ] ]}   # [7,8] 整體插入進來, 特別注意這是二級列表
       
  • $each ( 給[7,8]加個 $each,注意看結果變化 )

    M: table.update({'1': 1}, {'$addToSet': {'lin': {'$each':[7, 8]} }})
    P: 同上 
    結果 => {"1": 1, "lin": [7,8]}  # 7,8單獨插入進來,參考python的 * 解構
           
  • $push(數據添加, 比$addToSet強大,可任意位置,可重複)

    """
        補充說明: 
            $addToSet:添加數據有重複,會自動去重
            $push    :添加數據有重複,不會去重,而是直接追加
    """
    原始數據: {'1':1}
       
    M: table.update(
       { '1': 1 },
       {
         '$push': {
           'lin': {
              '$each': [ {'a': 5, 'b': 8 }, { 'a': 6, 'b': 7 }, {'a': 7, 'b': 6 } ],
              '$sort': { 'a': -1 },
              '$position': 0,
              '$slice': 2
    }}})    # 這裏爲了清晰點,我就把所有括號摺疊起來了  
    P: 同上
    
    結果 =>   {"1" : 1, "lin" : [ { "a" : 7, "b" : 6 }, { "a" : 6, "b" : 7 } ] }
    終極解析:
        1. 添加數組: 先走 $sort => 根據a 逆序排列
        2. 再走 $position,  0表示:索引定位從0開始
        3. 再走 $slice, 2表示: 取2個
        4. 最後走 $each,把數組元素逐個放進另一個數組,說過的,相當於python的 * 解構操作, 
    
    
  • $pop(只能 刪除 頭或尾 元素)

    M: table.update({'a': a}, {'$pop': {'lin': 1}})        # 刪除最後一個
    P: 同上
       
    注1:$pop參數, 1代表最後一個,  -1代表第一個。 這個是值得注意一下的,容易記反
    注2:如果全部刪沒了,那麼會剩下空[], 而不是徹底刪除字段
           
  • $pull (刪除 任何位置 的 指定的元素)

    M: table.update({'1': 1},{'$pull':{ 'lin':[7,8]}})   # 刪除數組中[7,8]這個內嵌數組
    P: 同上
  • $pullAll(基本和 $pull 一致)

    M: table.update({'1': 1},{'$pullAll':{ 'lin':[ [7,8] ]}})   # 同$pull,但多了個 []
    P: 同上
    
    注: $pull 和 $pullAll 針對於 內嵌文檔 和 內嵌數組 有細小差別, 差別如下:
        內嵌數組: 
            $pull 和 $pullAll 都嚴格要求內嵌數組的 排列順序,順序不一致,則不返回
        內嵌文檔:  
            $pullAll : 嚴格要求內嵌文檔的順序, 順序不一致,則 不返回
            $pull    : 不要求內嵌文檔的循序,   順序不一致,一樣可以返回

"""
    第一個參數的條件是 篩選出 數據的記錄(文檔)
    第二個參數的條件是 篩選出 數據的記錄中的 屬性(字段),不配置 就是 默認 取出所有字段
    find({查詢條件}, {投影設置}) 
"""
  • 投影解釋

    哪個字段 設置爲 0, 此字段就不會被投影, 而其他字段全部被投影
    哪個字段 設置爲 1, 此字段就會被單獨投影, 其他字段不投影
    {'name': 0, 'age': 0}      # 除了 name 和 age  ,其他字段 都 投影
    {'name': 1, 'age': 1}      # 只投影 name 和 age, 其他字段 不 投影,(_id除外)
    
    注意:所有字段必須滿足如下要求:
        一: 你可以不設置,默認都會被投影
        二: 如果你設置了,就必須同爲0,或者同爲1,不允許0,1 混合設置(_id除外)
        三: _id雖然可以參與混合設置,但是它只可以設爲0, 不可以設爲1,因爲1是它默認的
    
    通俗理解(0和1的設定):另一種理解思想 ====> 
        設置爲1:  就是 加入 白名單 機制
        設置爲0,  就是 加入 黑名單 機制
     
    注: _id字段是 MongoDB的默認字段,它是會一直被投影的(默認白名單)
        但是,當你強制指定 {'_id': 0}    ,強制把 _id指定爲0,他就不會被投影了(變爲黑名單)
    
    語法:
        M: queryset = table.find({}, {'name': 0})
        P: 同上
    
  • 投影-數組切片($slice)

    """針對投影時的value爲數組的情況下,對此數組切片,然後再投影"""
    數據條件: {'arr1': [5,6,7,8,9] }
    整形參數:
        M: queryset = table.find({},{'arr1':{'$slice': 2}})     # 2表示前2個, -2表示後兩個
        P: 同上,一模一樣,一字不差
        結果: { 'arr1': [5,6] }
    數組參數: [skip, limit]    
        M: queryset = table.find({},{'arr1':{'$slice': [2,3]}}) # 跳過前2個,取3個
        P: 同上,一模一樣,一字不差
    
        輸出結果 =>  { 'arr1': {7,8,9] }
     
        注: 這種數組參數,你可以用 skip+limit 方式理解
             也可以用, python的索引+切片方式理解 (skip開始查索引(0開始數), 然後取limit個)
    
  • 投影-數組過濾($elemMatch)

    """
     針對投影時 的value爲數組的情況下,根據指定條件 對 數組 過濾,然後再投影
     注意這個過濾機制: 從前向後找,遇到一個符合條件的就立刻投影(類似 python正則的 search)
    """
    數據條件: {'arr1': [6,7,8,9]}
    
    M: queryset = table.find({}, {'arr1': {'$elemMatch': {'$gt':5}} })
    P: 同上
    
    輸出結果 => "arr1" : [ 6 ]
    
    解析:(我自己總結的僞流程,可參考理解)
        1. 準備投影
        2. 發現數組,先處理數組,可看到數組中有 elemMatch條件
           elemMatch在投影中定義爲: 
           ”你給我一個條件,我把符合條件的 數組每個元素從前向後篩選
            遇到第一個符合條件的就返回, 剩下的都扔掉  (這裏的返回你可以理解爲 return)
           “
        3. 把 2 步驟 返回的數據 投影
    
  • limit()

    limit: (只取前n條)
        M: queryset = table.find({'name':'lin'}).limit(n)    # n就是取的條數
        P: 同上
  • skip()

    skip: (跳過n條,從第n+1條開始取)
        M: queryset = table.find({'name':'lin'}).skip(n)    # 從0開始數
        P: 同上
     
        解釋一下skip這個參數n:
            假如n等於2 ,就是從第三個(真實個數)開始取   => 你可以借鑑數組索引的思想 a[2]
  • count()

    count: (統計記錄數)
        M: count_num = table.find({'name':'lin'}).skip(1).limit(1).count()
        P: count_num = table.count_documents(filter={'name':'lin'}, skip=1, limit=1)
     
        分析:
            find()   -> 查出 3 條數據
            skip(1)  -> 跳過一條,就是從第二條開始取
            limit(1) -> 接着上面的來,從第二條開始取(算本身哦),取一個,實際上取的就是第二條
            count()  -> 3    # 也許你很驚訝,按常理來說,結果應該爲 1(看下面)
     
        count(applySkipLimit=false)    # 這是 API原型,這個參數默認爲False
            applySkipLimit: 看名字你就知道這函數作用了吧
                默認不寫爲 False: 不應用(忽略) skip(), limit() 來統計結果 ==> 上例結果爲 3
                設爲 True:           結合 skip(), limit() 來統計最終結果 ==> 上例結果爲 1
     
        注: 對於 count()  ,Mongo 和 PyMongo都有此方法,且用法是一模一樣的。
             那爲什麼上面PyMongo中我卻用了 count_documents() 而不是 count()  ?????
             答:
                 因爲 運行 或者後 戳進PyMongo源碼可清晰看見,未來版本 count() API將要廢除。
                 官方建議我們用  count_documents()
                 它的好處是把 skip() 和 limit() 由兩個函數調用 變爲 2個參數傳進去了。
    
  • sort()

    sort: 排序
    M: queryset = table.find({'name':'lin'}).sort({'_id': -1})  # 注意,參數是{} 對象
    P: queryset = table.find({'name':'lin'}).sort( '_id', -1 )    # 注意,這是2個參數
        第一個參數,代表 排序依據的字段屬性
        第二個參數,代表 升/降  
            1 : 升序      eg: 456
            -1: 降序      eg: 654
    
    特別注意: 3連招順序(優先級要牢記)  ()
    sort -> skip -> limit   (排序 - 定位 - 挑選) 無論你代碼什麼順序,它都會這個順序執行
    eg: queryset = table.find({'name': 'lin'}).sort('_id', -1).skip(1).limit(1)
    
    也許你會有這樣一個疑惑: 爲什麼 count_documents 沒有放進連招裏面?
    答:
        你仔細想想, 統計個數,和你排不排序有關係嗎?  
        沒錯,一點關係都沒有。。。     sort() 和 count() 沒有聯繫
    
  • 數組操作符

    已有數據條件: { name: ['張','李','王'] }
    
    $all: 
       M: queryset = table.find({'name': {'$all': ['張','李']}})  # 數組值裏必須包含 張和李
       P:同上,一模一樣,一字不差
    $elemMatch:
       M: queryset = table.find({'name': {'$elemMatch': {'$eq':'張'} }}) # 數組值有張 就行
       P: 同上,一模一樣,一字不差
    
  • 正則

    M: db.xx.find( {name: { $regex: /^a/, $options:'i' }} )
    P: queryset = db.xx.find({'name': {'$regex': 'LIN', '$options': 'i'}})
    PyMongo版的或者這樣寫->
        import re
        e1 = re.compile(r'LIN', re.I)      # 把Python的正則對象 代替 Mongo語句
        queryset = db.xx.find({'name': {'$regex': re1 }}) 
    

聚合

  • 聚合表達式

  • 字段路徑表達式:

       $name    # 具體字段
  • 系統變量表達式:

       $$CURRENT # 表示管道中,當前操作的文檔
  • 反轉義表達式:

       $literal: '$name'    # 此處 $name 原語法被破壞,現在它只是單純的字符串
    
  • 聚合管道

       """
           單個管道,就像 Python中的 map等高階函數原理, 分而治之。
           只不過,MongoDB善於將管道串聯而已。
           .aggregate([ 裏面寫管道各種操作  ])
       """
  • $match(管道查詢)

       M: queryset = table.aggregate([{'$match': {'name': 'zhangsan'}}])
       P: 同上
       
  • $project(管道投影)

       數據條件 => 
       [
           {"id":'xxx', "name" : "zhangsan", "age" : 15 },
           {"id":'xxx', "name" : "lisi", "age" : 18 },
           {"id":'xxx', "name" : "wangwu", "age" : 16 }
       ]
       M: queryset = table.aggregate([{'$project': {'_id': 0,'new':'5'}}])
       P: 同上
       
       結果 => [{'new': '5'}, {'new': '5'}, {'new': '5'}]
       注:'new'是在投影的時候新加的,會被投影。但是加了此新值,除了_id,其他屬性默認都不會被投影了
    
  • $skip (管道跳過,原理同前面講過skip() 略)

  • $limit(管道截取,原理同前面講過的limit() )

       M: queryset = table.aggregate([{'$skip': 1},{'$limit':1}])
       P: 同上
       解釋:
           一共三條文檔, skip跳過了第一條,從第二條開始取,limit取一條,所以最終取的是第二條
  • $sort (管道排序,同上,不解釋)

       M: queryset = table.aggregate([{'$sort':{'age':1}}])
       P: 同上
    
  • $unwind(管道展開數組, 相當於 數學的 分配律)

       數據條件 => {"name" : "Tom", "hobby" : [ "sing", "dance" ]}
       
       path小參數:
           M: table.aggregate([{'$unwind':{'path': '$hobby'}}])   # 注意 path是語法關鍵詞
           P: 同上
           結果 => 
               { "_id" : xx, "name" : "Tom", "hobby" : "sing" }
               { "_id" : xx, "name" : "Tom", "hobby" : "dance" }
           形象例子:
               a * [b+c] => a*b + a*c
       
       includeArrayIndex小參數:
           M: queryset = table.aggregate([{'$unwind': {
                       'path':'$hobby', 
                       'includeArrayIndex':'index'    # 展開的同時會新增index字段記錄原索引       
               }}])
           P: 同上
           結果 => 
               {"name" : "Tom", "hobby" : "sing", "index" : NumberLong(0) }
               {"name" : "Tom", "hobby" : "dance", "index" : NumberLong(1) }    
               
       注意:
           $unwind 上面有兩種特殊情況:
           情況一:
               文檔中無 hobby字段   或   hobby字段爲 空數組[]
               那麼該文檔不參與unwind展開操作, 自然就不會顯示結果。
               若想讓這種文檔也參與 unwind展開操作,那麼需要追加小參數 
                   'preserveNullAndEmptyArrays':true        # 與 path同級書寫
               最終結果,這種字段的文檔也會被展示出來,並且 index會被賦予一個 null值
           情況二:
               文檔中有 hobby字段,但是該字段的值並不是數組
               那麼該文檔 會 參與 unwind展開操作,並且會顯示出來, 同樣 index 會被賦予一個 null值
    
  • $lookup(使用方式一)

       使用方式(一):集合關聯 ===> 我的理解是,相當於關係型數據庫的 多表查詢機制
    
           集合 <=> 表  ,  多表查詢 <=> 多集合查詢 
               自身集合 與 外集合 根據我們指定的 關聯字段 關聯後, 如有關聯,
               則新字段的值爲 [外集合的關聯文檔, 。。。], 有幾條文檔關聯,這個數組就會有幾條
    
       廢話不多說,先重新創建兩個集合:
       db.user.insertOne({'name':'貓', 'country': ['China','USA']})    # 一條
       db.country.insertMany([{'name':'China'}, {'name':'USA'}])      # 兩條
       
       table = db.user        # 看好,我賦值了一下,下面直接寫table就行了
       
       M: queryset = table.aggregate([{
           '$lookup': {
               'from': 'country',           # 需要連接的另外一個集合的名稱(外集合)
               'localField': 'country',     # (主集合)連接的 依據 字段
               'foreignField': 'name',      # (外集合)連接的 依據 字段
               'as': 'new_field'            # 最終關聯後查詢出來的數據,生成新字段,as用來起名
           }
       }])
       P: 同上
       
       結果 => 
       {
           "_id" : ObjectId("5d2a6f4dee909cc7dc316bf1"),
           "name" : "貓",
           "country" : [
               "China",
               "USA"
           ],                  # 這行之前應該不用解釋,這就是 user集合本身的數據,沒變
           "new_field" : [     # 這行是新加的字段,後面解釋
               {
                   "_id" : ObjectId("5d2a6fcbee909cc7dc316bf2"),
                   "name" : "China"
               },
               {
                   "_id" : ObjectId("5d2a6fcbee909cc7dc316bf3"),
                   "name" : "USA"
               }
           ]    
       }    
       解釋:
           1. new_field是我們新添加的字段
           2. 因爲user集合和country集合 我們給出了2個依據關聯字段
              並且這兩個關聯字段 'China' 和 'USA' 的值都相等
              所以最終 user集合的new_field字段中 會添加 兩條 country集合的文檔 到 [] 中
           3. 如果無關聯, 那麼   new_field字段中的值  爲  空[]
           
  • $lookup(使用方式二):

       使用方式二:不做集合的關聯,而是直接把(外集合)經過條件篩選,作爲新字段放到(主集合)中。
       
       M: queryset = table.aggregate([{
           '$lookup': {
               'from': 'country',                # 外集合
               'let': {'coun': '$country'},      # 使(主集合)的變量 可以放在(外集合)使用
               'pipeline': [{                    # 外集合的專屬管道,裏面只可以用外集合的屬性
                   '$match': {                   # 因爲設置了 let,所以這裏面可以用主集合變量
                       '$expr': {                # $expr使得$match裏面可以使用 聚合操作
                           '$and': [
                                   {'$eq': ['$name', 'China']},   # 注意,這是聚合的 $eq用法
                                   {'$eq': ['$$coun',['China', 'USA']]}
                           ]
                       }
                   }
               }],
               'as': 'new_field'
           }
       }]) 
       P: 同上
       解釋:
           把(外集合) pipeline裏面按各種條件 查到的文檔, 作爲(主集合)new_field 的值。
           當然,如果不需要主集合中的屬性,可以捨棄 let 字段
    
  • $group (分組--統計種類)

       用法1(分組--統計字段種類)
           M: queryset = table.aggregate([{'$group': {'_id': '$name'}}])    # _id是固定寫法
           P: 同上
           結果 => [{'_id': '老鼠'}, {'_id': '狗'}, {'_id': '貓'}]
           
       用法2(分組--聚合)
           數據條件:
               { "name" : "貓", "country" : [ "China", "USA" ], "age" : 18 }
               { "name" : "狗", "country" : "Japna" }
               { "name" : "老鼠", "country" : "Korea", "age" : 12 }
               { "name" : "貓", "country" : "Japna" }
       
           M: queryset = table.aggregate([{
               '$group': {
                   '_id': '$name',                    # 根據name字段分組
                   'type_count': {'$sum': 1},         # 統計每個分類的 個數
                   'ageCount': {'$sum': '$age'},      # 統計age字段的 數字和
                   'ageAvg': {'$avg': '$age'},        # 統計age字段的 平均值
                   'ageMin': {'$min': '$age'},        # 統計age字段的 最小值
                   'ageMax': {'$max': '$age'},        # 統計age字段的 最大值
               }
              }])
           p: 同上
           
           結果:
                       {
                           "_id" : "老鼠",
                           "type_count" : 1,
                           "ageCount" : 12,
                           "ageAvg" : 12,
                           "ageMin" : 12,
                           "ageMax" : 12
                       }
                       {
                           "_id" : "狗",
                           "type_count" : 1,
                           "ageCount" : 0,
                           "ageAvg" : null,
                           "ageMin" : null,
                           "ageMax" : null
                       }
                       {
                           "_id" : "貓",
                           "type_count" : 2,
                           "ageCount" : 18,
                           "ageAvg" : 18,
                           "ageMin" : 18,
                           "ageMax" : 18
                       }
           注意:
               若想直接對整個集合的 做統計,而不是分組再統計
               把 _id改爲 null即可  { _id: 'null' }      
               # (或者隨便寫一個匹配不到的 字符串或數字都行,分不了組,就自動給你統計整個集合了)
    
  • $out (聚合操作後,將結果寫入新集合)

       """
           我的理解是重定向 操作, 或者理解爲 視圖 操作
           寫入的集合如果存在,那麼會全部覆蓋(但保留索引)
           聚合過程遇到錯誤,那麼會自動執行 ’回滾’操作
       """
       M: 
           table.aggregate([
               { '$group': {'_id': '$name'} },
               { '$out': 'newCollection' }
           ])
       P: 同上
       最後驗證: db.newCollection.find()   ,你就會看到新集合 及其 裏面的內容
    
       聚合管道 ==> 第二個參數
           table.aggregate([之前說的都是這裏面的參數],  下面說這個參數)
           
           allowDiskUse: true
               每個聚合管道佔用內存需 < 16M, 過大就會出問題
               allowDiskUse設置爲true, 會將內存的 寫入到臨時文件中,減緩內存壓力。
    
               官方文檔:write data to the _tmp subdirectory in the dbPath directory
                        Default: /data/db on Linux and macOS, \data\db on Windows
               它說: 默認在  dbPath配置變量下的 子目錄_tmp下,  dbPath默認爲 : /data/db
           
           M:
               queryset = table.aggregate([{
                   '$group': {'_id': '$name'}}],
                   {'allowDiskUse': true}           
               )
           P:     
               queryset = table.aggregate([{
                   '$group': {'_id': '$name'}}],
                   allowDiskUse=True,                 # 注意,這裏語法稍有不一樣
               )
       

索引

  • 創建索引:

  • 單鍵索引

      M: table.createIndex({'name':1})
      P: table.create_index([('name',-1)])        # -1代表逆序索引,注意是元組
          
  • 聯合索引

      索引命中:最左匹配原則  eg  1,2,3  這三個創建聯合索引, 可命中索引爲:【1,12,123】
      M: table.createIndex( {'name':1}, {}, {} )           # 多個{}
      P: table.create_index([ ('name',-1), (), () ])       # 多個元組
          
  • 多鍵索引

      多鍵是針對於數組來講的,創建單鍵的字段 指定爲 數組字段, 默認就會設置爲多鍵索引
    
  • 唯一索引 (unique)

      '''注意: 如果集合中,不同文檔的字段有重複,創建唯一索引的時候會報錯'''
      M: table.createIndex({'name':1}, {'unique':true})
      P: table.create_index([('name', 1),('counrty',1)], unique=True)
          
  • 稀疏索引 (sparse)

      eg:
      一個集合中:
          給 name創建 唯一索引
          插入文檔1: 有 name字段
          插入文檔2: 無 name字段 (MongoDB會在索引庫中,把沒有的字段的 索引設爲 {字段:null}  )
    
          再插入文檔3, 無name字段  --> 同樣也會把索引庫中 name設爲 null  
              但是就在這個時候,剛要把索引庫中的 name字段設爲 null的時候。。。
              
              唯一索引告訴你:” 我這裏已經有了一個,{ name:null },請你滾 ”
              然後就無情的給你報錯了(重複索引字段)
    
          那咋整啊, 別急,稀疏索引就是給你辦這事的
          
          設置稀疏索引。 MongoDB就不會把  沒有的字段 加入到索引庫了
          所以,索引庫裏面就不會自動添加  {字段: null} 
          重新再次插入文檔3, 無name字段, 可成功插入,不存在null的重複問題了
    
          M: table.createIndex({'name':1}, {'unique':true, 'sparse':true})
          P: table.create_index([('name', 1),('counrty',1)], unique=True, sparse=True)
    
  • 查詢索引

      M:queryset = table.getIndexes()
      P: queryset = table.list_indexes()
    
  • 刪除索引

      方式1:
          M: table.dropIndex('索引名')     # 索引名可通過 上面查詢索引的指令查
          P: table.drop_index('索引名')    
      方式2:
          M: table.dropIndexes()          # 刪除全部,_id除外, 想指定刪除多個,可用列表列出
          P: table.drop_indexes()
    
  • 查看索引性能(是否有效)

      table.上面說過的任一函數().explain()           # 鏈式調用 explain,表示列出此操作的性能
      eg:
          M: queryset = table.explain().find({'name':'貓'})
          P: 同上
      結果中找到:            
          queryPlanner -> winningPlan -> inputStage -> stage   # stage結果對應說明如下
              COLLSCAN    # 未優化,還是搜的整個集合
              IXSCAN      # 索引起到作用
              
      索引對投影的優化:
          queryPlanner -> winningPlan -> stage   # stage結果對應說明如下
              FETCH         # 索引 對投影 未優化
              PROJECTION    # 索引 對投影 起到優化作用
              
      索引對排序的優化:
          同上 stage  最好 不是 sort
          按索引 正序(逆序) 取數據, 這樣就有效避免了機械排序的過程
    
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章