经验拾忆纯手工-MongoDB与PyMongo语法对比解析

8次阅读

共计 13513 个字符,预计需要花费 34 分钟才能阅读完成。

阅读须知

由于是对比书写:
    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
          按索引 正序(逆序)取数据,这样就有效避免了机械排序的过程
    
    
正文完
 0