MongoDB 查询指令

作者: g_s_007 | 来源:发表于2019-09-26 23:53 被阅读0次

    本文将《mongodb权威指南(中文第二版)》中用到的指令加以总结,并结合示例

    简介

    • 面向文档(document-oriented)而不是关系型数据库,目的:利于扩展以及其他好处
    • 不再有行(row) 的概念 而是 文档(document)模型
    • 易于扩展:横向和纵向扩展(scale out,scale up)
      • 纵向扩展就是使用计算能力更强的机器
      • 横向扩展要增加存储空间或者提高性能
      • mongo 的设计采用横向扩展,面向文档的数据模型使它能够很容易的在多台服务器之间进行数据分割
    • 功能丰富
      • 索引 indexing
      • 聚合 aggregation
      • 特殊的集合类型
      • 文件存储 file storage

    基础知识

    • 文档:document,基本单元
    • 集合:collection 可以看作一个动态模式的表
    • 数据库:database,每一个数据库都拥有自己的集合
    • _id: 这个键在文档所属的集合中是唯一的
    • 自带简单且功能强大的javaScript shell 用于管理实例或者数据操作(后面会讲到)

    1. 文档

    mongo 的核心概念,键值对的有序集,文档的键是字符串,除极少数可以是任意UTF-8字符
    注意如下:

    • 键不能含有\0(空字符,这个字符用于表示键的结尾
    • . 和 $ 具有特殊意义,只能在特定环境下使用
    • mongo 区分类型和大小写
    • mongo 的文档不能有重复的键

    2. 集合

    • 集合就是一组文档
    • 集合是动态模式的,也就是说集合中的文档格式可以不同,随之而来的问题是:我们需要多个集合吗?
    • 我们使用多个集合的目的有以下几条:
      • 相同格式的文档放入同一集合便于查询和管理,数据也更加集中
      • 查询数据时,查询特定的集合比查询一个集合中的特定中特定类型的文档要快的多
      • 更加有效的创建索引
    • 集合命名
      • 集合名不能时空字符串("")
      • 集合名不能包含\0 空字符
      • 集合名不能以"system."开头,这是为系统集合保留的前缀
      • 不要在集合中含有$ 保留字符
    子集合

    使用"." 分隔不同命名空间的子集合,很多mongoDB工具都使用了子集合

    • GridFS , 大文件存储协议就是使用子集合来存储文件的元数据

    3. 数据库

    image.png
    • admin

    安装好mongo 后,使用show dbs 可以看到数据库,系统默认会有如下几个数据库

    image.png

    详细的介绍后面再补充 todo

    tips :
    mongo 集合命名空间不得超过121字节,在实际应用中应该要小于100字节

    启动mongoDB

    • 安装mongo
    • 启动服务器:$ mongod
    • 启动客户端: $ mongo
    • 注意:
      • mongod 在没有参数的情况下会使用默认数据目录:/data/db,如果没有会造成启动失败,可以自己创建一个
      • mongoDB 监听27017端口
      • mongo 还会启动一个非常基本的http服务器,监听数字比主端口高1000的端口:28017 ,可通过: http://localhost:28017 能获取数据库的管理信息

    mongoDB shell

    mongoDB 自带javascript shell,几乎所有的数据库的操作都可以通过shell 完成

    • 显示当前数据库:> db
    • 使用数据库:use videos
    • 基本操作:创建,读取,更新和删除:crud
    删除数据库
    use 数据库
    db.dropDatabase()
    

    集合(表)操作

    创建(可以直接通过插入数据创建)
    db.createCollection(集合名)
    
    显示集合列表
    show collections
    
    删除集合
    db.集合名.drop()
    
    查询

    mongo shell 中查看find下的方法:

    image.png
    • 基本查询
    db.集合名.find() # 查询所有数据,自动显示最多20个,it 下一页
    db.集合名.findOne() # 查询一个
    
    • 条件查询
    # 比较运算符:$gt,$gte,$lt,$lte,$ne:大于,大于等于,小于,小于等于,不等于,$ne 能用于所有类型的数据
    # 逻辑运算符:$or :或
    # 范围运算符:$in,$nin
    # 正则表达式:/^SRR/ 或者 {$regex:"^SRR"} //样本搜索
    # 正则表达式查询不区分大小写:/^na/i 或者: {$regex:"^na",$options:"$i"}
    # 正则表达式匹配贪婪匹配:不止srr还有srry: /^srry?/i
    # $not 是元条件句,可以用在任何其他条件之上
    # $mod 取模运算符,会用查询的值除以第一个给定值,若余数等于第二个给定值就返回匹配成功的记录
    
    db.samples.find({"size":{$gt:18}})
    
    db.blog.find({"paper":{$in:[4,5,6,7,8,9,10]}})
    
    db.blog.find({$or:[{"paper":6},{"title":"my blog"}]}})
    
    db.blog.find({$or:[{"paper":{$in:[5,6,7,8,9]}},{"title":"my blog"}]}})
    
    db.people.find({"type":"dvd","release":{$mod:[2,0]}}) # 将release 除以2,如果余数为0则匹配成功并返回
    
    # 使用$not 对上面的语句取反
    db.people.find({"release":{$not:{$mod:[2,0]}}}) 
    
    

    总结:条件语句是内层文档的键,修改器是外层文档的键,也就是一个键可以有任意多个条件,但是一个键不能对应多个更新修改器。
    $and,$or 这种元操作符也位于外层文档中。

    • null 类型查询
    1. 某个键的值是null, 根据{k:null} 去查询此条记录,会返回所有的包含或者不包含此键的记录。
    2. 如果只想匹配含有{k:null} 的这条记录,需要通过$exit限定一下
    db.people.find({"yyy":{$in:[null],"$exists":true}}).pretty()
    db.people.find({"yyy":{"$eq":null,"$exists":true}}).pretty()
    
    • 自定义查询
    # 略 使用js 语句
    
    • skip和limit 查询
    # limit 和 skip: 总是先执行skip 再执行limit 
    
    db.samples.find().limit(2).skip(1) # 过掉第一个,显示下面两个
    
    • 显示指定字段
    db.samples.find({},{"name":"可以是任意字符串"}) # 显示name 字段
    或者一般就写:db.samples.find({},{"name":1})
    
    db.sample.find({},{"name":0}) # 显示除了name 的其他字段也就是不显示name 
    
    
    • 排序
    db.sample.find({"name":"SRR100001"}).sort({"create_time":1,"name":-1}) # 1 为升序,-1为降序
    

    这条语句是shell会转换成{$query:{"name":"SRR10001"},"$orderby":{"create_time":1}}去执行。

    • 计数
    db.samples.find({条件}).count() 
    # 或
    db.samples.count({条件}) 
    
    • 去重
      第一个参数描述去重字段,第二个参数查询条件

    db.stu.distinct('hometown',{age:{$gt:18}})

    • 使用数组元素查询数组
    # 只要含有此元素的数组都会返回
     db.people.find({"friuts":"apple"})
    
    • $all

    如果需要多个元素来匹配数组就使用$all

    db.people.find({"friuts":{$all:["spple","banana"]}}) # 感觉和$in 效果一样?
    
    # 使用数组下标查询数组特定位置的元素
    db.people.find({"friuts.2":"peach"}) # 查找数组下标2对应元素是peach 的记录
    
    • $size

    查询特定长度的数组,但不能与$gt 等查询条件组合使用,假如使用一个字段size 来记录插入数据的个数,也就是数组的长度就可以使用size 来获取了,但是数组元素的有可能重复。

    # 查询数组长度是2的记录
    db.people.find({"friuts":{$size:2}})
    
    # 插入一个数组元素,size 加1
    db.people.update({"_id" : ObjectId("5c492d47faf6c2368981fb9e")},{$push:{"friuts":"apple"},$inc:{"size":1}})
    
    db.people.find({"size":{"$gt":2}})
    
    
    • $slice

    $slice 切片操作会返回某个数组的子集

    # 显示数组comments 前三个元素
    db.blog.find({"_id" : ObjectId("5c491aa3faf6c2368981fb9c")},{"comments":{"$slice":3}}) 
    
    # 显示后3个元素
    db.blog.find({"_id" : ObjectId("5c491aa3faf6c2368981fb9c")},{"comments":{"$slice":-3}})
    
    # 显示最后一个元素
    db.blog.find({"_id" : ObjectId("5c491aa3faf6c2368981fb9c")},{"comments":{"$slice":-1}})
    
    # 显示第二个到第四个元素
    db.blog.find({"_id" : ObjectId("5c491aa3faf6c2368981fb9c")},{"comments":{"$slice":[1,3]}})
    

    可以使用$ 操作符得到一个匹配的记录,但只能返回第一个匹配的文档

    db.blog.find({"comments.name":"jim"},{"comments":1})

    • 查询条件范围内满足的记录

    如果是查询文档中的标量(也就是非数组元素)使用 $gt,$lt...没问题,但是假如查询的文档中的字段是一个数组的话,会不准确。

    # 不准确的例子
    {"x" : 5}
    {"x" : 15}
    {"x" : 25}
    {"x" : [5, 25]}
    > db.test.find({"x":{"$gt":10,$lt:20}}) # 我们希望返回的是{"x" : 15},但是返回的是:{"x" : [5, 25]},{"x" : 15}两个,
    这是因为25满足条件大于10,5满足条件小于20。
    

    解决:

    1. 使用 $elemMath ,但不会匹配非数组元素,所以在包含不止数组元素的比较中并不准确

    db.blog.find({"paper":{$elemMatch:{$gt:3,$lt:10}}})

    1. 使用索引加min() 和 max(),推荐。性能和准确性很赞。

    db.test.find({"x" : {"$gt" : 10, "$lt" : 20}).min({"x" : 10}).max({"x" : 20})

    此查询只会遍历位于10到20的索引,不再与5和25比较。

    • 查询内嵌文档
    "name" : {
            "first" : "joe",
            "last" : "bufei"
        }
    # 查询满足first 为joe 的所有文档,点(.)表示进入内嵌文档内部的意思    
    db.blog.find({"name.first":"joe"})
    
    # 使用如下查询语句并不能唯一确定一个文档记录,会返回满足条件7和100 的所有文档记录
    db.school.find({"students.age":7,"students.school":100})
    
    # 使用 $elemMatch 确定唯一符合条件的文档记录
    db.school.find({"students":{"$elemMatch":{"age":7,"school":100}}})
    
    # 在find 的第二个参数中使用,过滤出仅想要的数据
    # 参考:https://wzgdavid.iteye.com/blog/2089455 
    
    db.school.find( { zipcode: "63109" },{ students: { $elemMatch: { school: 102}}})  # 只显示students 列表中符合条件的元素
    
    db.school.find({ zipcode: "63109"},{ students: { $elemMatch: { school: 102 } } }).sort( { "students.age": -1 }) # 倒序
    
    db.schools.find( { zipcode: "63109" },{ students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )
    
    
    • $where 查询

    键值对是一种表达能力非常好的查询方式,但是有时还是无法满足需求,此时就是$where 登场的时候了。

    用它可以在查询中执行任何js 代码,这样就能在查询中做几乎任何事情,但是为了安全起见,应该严格限制或者消除$where的使用。

    不是非常必要时,不建议使用$where 查询,查询速度很慢。并且不能使用索引。

    进行复杂查询的另一种方法就是使用聚合工具。

    • 随机选取文档

    一种方法是将文档的总数算出来之后,skip随机数获取,但是这种效率比较低,并且费时。
    另一种方法就是在插入文档的时候给每个文档都添加一个额外的随机键。

    下面两个方法都是随机产生0~1的随机数:

    db.people.insert({"name":"joe","random":Math.random()})

    pymongo

    coll.insert_one({"name": "11111","random":random.random()})

    这样的话,只要将查询条件设成在返回内的随机数,就可以了。

    db.people.findOne({"random":{"$gt":Math.random()}})

    db.people.findOne({"random":{"$lt":Math.random()}})

    还有一个方法是使用索引,这个稍后再谈

    • $maxScan :integer

    指定本次查询中扫描文档数量的上限,如果不希望查询耗时太多,也不确定集合中到底有多少文档需要扫描,那么可以使用,这样就会将查询结果限定为与被扫描的集合部分相匹配的文档,弊端:也许你需要并没有扫描到。。

    db.people.find()._addSpecial("$maxScan",2) # 找出两个

    结果等效于:

    db.people.find().limit(2)

    pymongo 中:

    cursor = coll.find({})
    a = cursor.max_scan(2)
    

    相关文章

      网友评论

        本文标题:MongoDB 查询指令

        本文链接:https://www.haomeiwen.com/subject/rlxoectx.html