资讯专栏INFORMATION COLUMN

经验拾忆(纯手工)=> MongoDB与PyMongo语法对比解析

mo0n1andin / 3575人阅读

摘要:举个栗子你有一个箱子,里面有一个儿子级别和孙子级别的箱子共层现在你把孙子级别的箱子多带带拿出来,把整个箱子替换掉就是这种思想。。。自己体会吧这种语法,好像列表的切片赋值。。官方建议我们用它的好处是把和由两个函数调用变为个参数传进去了。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/45195.html

相关文章

  • 经验拾忆手工)=&gt; Python基本数据类型

    摘要:不要疑惑,告诉你答案这个代表正负号的正。虽然一点技术含量没有,但是你要懂序列也许叫可迭代对象更为合适,但是我喜欢叫序列。 数据结构 可变类型与不可变类型(重头戏) 基操: 可变类型:[], {} # 可增删改 查 不可变类型: int float str () # 无法增删改, 只可查 升操: + 与...

    Andrman 评论0 收藏0
  • 经验拾忆手工)=&gt; Python正则全解详解

    预编译 import re re1 = re.compile(r元字符 组成的正则规则) # 元字符下面会说 re1.方法() # 方法下边也会说 元字符: 表示普通字符: . # 除了 外 都可以匹配的到 d # 只匹配 纯数字 0-9 D # 和 d相反, 除了数字全都匹配 ...

    Luosunce 评论0 收藏0
  • 经验拾忆手工)=&gt; Python高阶函数操作

    摘要:解释就相当于把每个序列元素的每一个单独用一个管道函数处理,再把他们按顺序组合成一个新可迭代对象注意这个管道函数只能是单参数函数,如果想传递多个参数怎么办使用偏函数怕有些人看不懂,这里就不用了,而是用普通函数定义方式固定值固定值固定值固定值固 map In [25]: list(map(lambda a:a**2, [1,2,3,4])) Out[25]: [1, 4, 9, 16] 解...

    Elle 评论0 收藏0
  • 经验拾忆手工)=&gt; Python三程

    摘要:多线程对于爬虫方面也可以表现出较好的性能。计算密集型就别想多线程了,一律多进程。所以同一时刻最大的并行线程数进程数的核数这条我的个人理解很模糊,参考吧多线程多线程有种通过的那种方式,非常普遍,此处就不写了。 GIL的理解 GIL这个话题至今也是个争议较多的,对于不用应用场景对线程的需求也就不同,说下我听过的优点: 1. 我没有用过其他语言的多线程,所以无法比较什么,但是对于I/O而言,...

    Snailclimb 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<