title: MongoDB高级查询介绍
date: 2017-12-3 22:14:19
tags:

  • MongoDB
  • MongoDB查询
    categories:
  • 数据库

在几乎所有的项目中对数据库的操作是必要且高频的,对数据库的操作可以大致可分为读和写,其中读的概率要远远大于写。我们选用非关系性数据库一个很重要的原因在于它的查询相对于关系性数据库来讲是非常高效的,它极大地减少了表之间联合查询,只要数据结构设计合理,很多时候一条查询语句即可得到你想要的结果。

基本查询

基本查询指的是通过 find 语句进行的查询,也是使用比较多的一种查询,类似于关系型数据库的 select

准备测试数据

为了方便进行语法示例,我们在数据库中装填一些数据。

{"_id" : ObjectId("5a23dff29369d959a2ec17a0"),"age" : 30.0,"label" : ["钓鱼","旅行"],"name" : "ZhanShan","nickname" : "张三"
},
{"_id" : ObjectId("5a23e0239369d959a2ec17a1"),"age" : 28.0,"label" : ["喝茶"],"name" : "LiShi","nickname" : "李四"
},
{"_id" : ObjectId("5a23e07b9369d959a2ec17a2"),"age" : 17.0,"label" : ["锻炼","泡吧","直播"],"name" : "WangWu","nickname" : "王五"
}

查询示例

  • findOne() 执行查询后仅返回一条数据,不管查询条件如何都只会返回一条结果。

    # 查询年龄大于18岁的用户
    > db.user.findOne({ age: { $gt: 18 } })
    {"_id" : ObjectId("5a23dff29369d959a2ec17a0"),"name" : "ZhanShan","nickname" : "张三","age" : 30,"label" : ["钓鱼","旅行"]
    }
    
  • find() 执行查询后返回多条数据,具体根据传入的查询来决定。
    > db.user.find()
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ] }
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ] }
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# 查询参数用类JSON对象来表示,例如 `{name: 'ZhanShan'}`,其中name可以省略引号。
    > db.user.find({name: 'ZhanShan'})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ] }# 指定结果返回的字段投影,这和sql中指定返回字段作用相同
    > db.user.find({ age: { $gt: 18 } }, {name:1})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan" }
    # 默认主键编号将被投影,如果不希望看到可以明确指定期可见性为0
    > db.user.find({ age: { $gt: 18 } }, {name:1, _id:0})
    { "name" : "ZhanShan" }# 比较操作 $lt $lte $gt $gte $ne,用来对字段值进行比较匹配,例如查询未满18周岁的用户。
    > db.user.find({ age: { $lt: 18 } }, {name: 1, age: 1})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "age" : 17 }
    # 对应上面比较操作的含义是小于、小于等于、大于、大于等于和不等于# $and 包含多个条件,之间是并的关系,例如查询成年人且爱好都是钓鱼的用户
    > db.user.find({$and:[{age:{$gte:18}}, {label:'钓鱼'}]})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ] }# $or 包含多个条件,之间是或的关系,用法和上面示例的类似。
    # $nor 相当于$or取反# $not 用作其他条件之上取反,例如查询不姓张的用户
    > db.user.find({nickname: {$not: /张/}})
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ] }
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# $mod 将查询的值除以第一个给定的值,如果余数等于第二个值则匹配成功,例如查询用户年龄是偶数的有哪些?
    > db.user.find({age: {$mod: [2, 0]}})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ] }
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ] }# $in 查询一个键的多个值,只要键匹配其中的一个即可,这类似于sql中的in查询。例如查询爱好是钓鱼或锻炼的用户
    > db.user.find({label: {$in: ['钓鱼', '锻炼']}})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ] }
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }
    # $nin为不包含,就是上面的条件取反。# $all 同时匹配多个值,也就是说要全部满足条件。例如同时有钓鱼和锻炼爱好的用户在目前的数据库中没有
    > db.user.find({label: {$all: ['钓鱼', '锻炼']}})
    ># $exists 过滤是否出现指定的字段,1取存在0取不存在,例如判断有填写手机号码的用户是哪些,示例数据中没有添加过phone字段,所以下面的查询肯定没有数据返回。
    > db.user.find({phone: {$exists: 1}})
    >
    # 如果我们为李四增加phone字段,则李四用户会被查询出来
    > db.user.update({name: 'LiShi'}, {$set: {phone: 13723458987}})
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > db.user.find({phone: {$exists: 1}})
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }# 对字段的模糊匹配,在sql中使用like关键字,在这里是使用正则匹配语法来操作的。例如查询姓王的用户有哪些?
    > db.user.find({nickname: /^王/})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# 如果字段值是数组形式,该怎样查询呢?
    # 单个元素匹配,直接指定值就可以了,例如看看爱好喜欢直播的用户有哪些?
    > db.user.find({label: '直播'})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# 多个元素匹配,同时都匹配。
    > db.user.find({label: {$all: ['直播', '喝茶']}})
    ># 多个元素匹配,只满足一个就可以了
    > db.user.find({label: {$in: ['直播', '喝茶']}})
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# 匹配数组中某个索引的值,记住此时字段名称+索引需要用引号引起来。
    > db.user.find({'label.1': '泡吧'})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ] }# 按数组长度匹配,例如查询只有一个爱好的用户
    > db.user.find({label: {$size: 1}})
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }# 使用$slice对数组切片,正数是前面多少条,负数是尾部多少条。这非常类似于Python中的列表切片操作,从数组中返回你指定的元素,这些你要的元素通过索引标识。仔细看下面的示例就比较容易理解了。
    > db.user.find({_id: ObjectId("5a23e07b9369d959a2ec17a2")}, {label: {$slice: 2}})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧" ] }
    > db.user.find({_id: ObjectId("5a23e07b9369d959a2ec17a2")}, {label: {$slice: 1}})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼" ] }
    > db.user.find({_id: ObjectId("5a23e07b9369d959a2ec17a2")}, {label: {$slice: 1}})# 使用$来指定符合条件的任意一个数组元素,即匹配的数组中仅返回查询到的值
    > db.user.find({label: '锻炼'}, {'label.$': 1})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "label" : [ "锻炼" ] }# $elemMatch 要求同时使用多个条件语句来对一个数组元素进行判断,我们添加一些测试数据以演示这个修饰符的使用方法。
    > db.user.update({name:'WangWu'}, {$set: {heartbeat: [60,67,59,69,71,73,80,90,91,55,101]}})
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    # 找出心跳值在80-90之间的用户
    > db.user.find({heartbeat:{$elemMatch:{$gte:80, $lte:90}}})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ], "heartbeat" : [ 60, 67, 59, 69, 71, 73, 80, 90, 91, 55, 101 ] }
    > db.user.find({heartbeat:{$elemMatch:{$gte:180, $lte:190}}})
    ># 查询内嵌文档,只需要在指定查询字段时用分隔符.进行区分即可,我们添加一些测试数据以方便。
    > db.user.update({name:'ZhanShan'}, {$set:{score:{'java':78.5, 'c++': 90}}})
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    # 注意像这种多字段需要用引号引起来,下面查找用户c++课程考试分数在90分以上的用户有哪些?
    > db.user.find({'score.c++': {$gte: 90}})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ], "score" : { "java" : 78.5, "c++" : 90 } }
    
  • count() 统计数据行
    > db.user.find().count()
    3
    
  • skip limit 分页返回数据,在项目中做查询的时候势必要进行分页的操作,这是个好习惯,不管程序员有没有指定分页参数,在DAO层中都必须有默认的分页值。
    # 例如每页只有2条数据,下面的参数表示第一页
    > db.user.find().skip(0).limit(2)
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ], "score" : { "java" : 78.5, "c++" : 90 } }
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }
    # 当查询第2页时,应该使用下面的参数
    > db.user.find().skip(2).limit(2)
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ], "heartbeat" : [ 60, 67, 59, 69, 71, 73, 80, 90, 91, 55, 101 ] }
    
  • sort() 排序,-1是降序,1是升序
    # 依据年龄字段升序
    > db.user.find().skip(0).limit(2).sort({age: 1})
    { "_id" : ObjectId("5a23e07b9369d959a2ec17a2"), "name" : "WangWu", "nickname" : "王五", "age" : 17, "label" : [ "锻炼", "泡吧", "直播" ], "heartbeat" : [ 60, 67, 59, 69, 71, 73, 80, 90, 91, 55, 101 ] }
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }
    > db.user.find().skip(0).limit(2).sort({age: -1})
    { "_id" : ObjectId("5a23dff29369d959a2ec17a0"), "name" : "ZhanShan", "nickname" : "张三", "age" : 30, "label" : [ "钓鱼", "旅行" ], "score" : { "java" : 78.5, "c++" : 90 } }
    { "_id" : ObjectId("5a23e0239369d959a2ec17a1"), "name" : "LiShi", "nickname" : "李四", "age" : 28, "label" : [ "喝茶" ], "phone" : 13723458987 }
    
  • distinct 取出不重复的数据 db.runCommand({distinct:'user', key: 'age'})

聚合查询

主要用来对集合中的文档进行变换和组合,从而对数据进行分析加以利用。

语法结构

db.集合名称.aggregate(构件1, 构件...) 由于聚合的结果要返回客户端,因此聚合的结果必须限制在16M以内。

准备测试数据

```json
> for (var i = 0; i < 100; i++) {
...     for (var j = 0; j < 4; j++) {
...         db.scores.insert({userId:"s" + i, course:"课程" + j, score:Math.random()*100})
...     }
... }
WriteResult({ "nInserted" : 1 })
> db.scores.find().count()
400
```

查询方法示例

由于本次准备的测试数据过多,在返回数据较多行时会省略显示,并附加 Type it for more 表示数据有多行。
```json
# 查找所有80分以上的学生,仅使用一个构件match
> db.scores.aggregate( {KaTeX parse error: Expected '}', got 'EOF' at end of input: match: {score:{gte:80}}} )
Type “it” for more

# 将每个学生的名字投影出来,使用两个构件match和project
> db.scores.aggregate({$match:{score:{$gte:80}}}, {$project:{userId:1}})
Type "it" for more# 对学生的名字分组,某个学生的名字出现一次就加1
> db.scores.aggregate({$match:{score:{$gte:80}}}, {$project:{userId:1}}, {$group:{_id:"$userId", count:{$sum:1}}})
{ "_id" : "s99", "count" : 1 }
{ "_id" : "s96", "count" : 1 }
{ "_id" : "s94", "count" : 1 }
Type "it" for more
# 对结果按照count进行降序
> db.scores.aggregate({$match:{score:{$gte:80}}}, {$project:{userId:1}}, {$group:{_id:"$userId", count:{$sum:1}}}, {$sort:{count:-1}})
Type "it" for more
# 只取前面三个
db.scores.aggregate({$match:{score:{$gte:80}}}, {$project:{userId:1}}, {$group:{_id:"$userId", count:{$sum:1}}}, {$sort:{count:-1}}, {$limit:3})
```

管道操作符

构件也即管理操作符,下面对其进行一个说明。
1. $match 对文档进行筛选,通常放到管道最前面的位置,在实际应用中要特别注意这一点,它可以显著优化查询性能。
1. $project 字段投影
1. 指定包含和排除字段 db.scores.aggregate({$project:{userId:1}})
1. 也可以重命名字段 db.scores.aggregate({$project:{userId2:"$userId"}})
1. $add $subtract $multiply $divice $mod 数学表达式

  • 例如给所有人加20分 db.scores.aggregate({$project:{userId2:"$userId", score:1,newScore:{$add:["$score", 10]}}}) 首先按条件匹配出集合,通过对集合的运算产生新字段并返回。
  • $year $month $week $dayOfMonth $dayOfWeek $dayOfYear $hour $minute $second 日期表达式,用于从指定字段中提取日期信息的表达式。使用方式不再赘述,可参考上面的示例。
  • $substr $concat $toLower $toUpper 字符串表达式,使用方式不再赘述,可参考上面的示例。
  • 逻辑表达式
    1. $cmp 比较两个表达式
    2. $strcasecmp 比较两个字符串
    3. $eq $ne $gt $gte $lt $lte
    4. $and $or $not
    5. $cond
    6. $ifNull
    7. $group 将文档依据特定的不同值进行分组。选定了分组字段过后,就可以把这些字段传递给 $group 函数的 _id 字段。
    db.scores.aggregate({$group:{_id:"$userId"}} )
    db.scores.aggregate({$group:{_id:{"myUserId":"$userId"}}} );
    db.scores.aggregate({$group:{_id:{"myUserId":"$userId"}, count:{$sum:1}}} ,{$sort:{count:-1}})
    
  • group支持的操作符 $sum $avg $max $min $first $last $addToSet $push
    1. $unwind 把数组中的每个值拆分成单独的文档 db.user.aggregate({$unwind:"$score"})
    2. $sort 如果要对大量文档进行排序,建议放到第一阶段以利用索引
    3. $count 用一返回集合中文档的数量
    4. $distinct 找出给定键的所有不同值,使用时必须指定集合和键

MapReduce

MongoDB也支持数据的并行计算,不过它并没有完整实现MapReduce的所有功能,但基本的功能可以满足一定的需求,可以简单了一下,虽然在实际项目中可能不会用到。

  • 找出集合中所有的键并统计每个键出现的次数

    > var map = function() {...         for (var key in this) {...                 emit(key, {"count": 1});
    ...         }
    ... };
    > var reduce = function(key, emits) {...         var total = 0;
    ...         for (var i in emits) {...                 total += emits[i].count;
    ...         }
    ...         return {"count": total};
    ... };
    > db.runCommand({mapreduce:'scores', map:map, reduce:reduce, out:'myout2'});
    {"result" : "myout2","timeMillis" : 464,"counts" : {"input" : 400,"emit" : 1600,"reduce" : 16,"output" : 4},"ok" : 1
    }
    > db.myout2.find()
    { "_id" : "_id", "value" : { "count" : 400 } }
    { "_id" : "course", "value" : { "count" : 400 } }
    { "_id" : "score", "value" : { "count" : 400 } }
    { "_id" : "userId", "value" : { "count" : 400 } }
    >
    

MongoDB高级查询介绍相关推荐

  1. MongoDB高级查询详细

    前言 前几篇,老玩家绕道即可,新手晚上闲着也是蛋疼,不如把命令敲一边,这样你就会对MongoDB有一定的掌握啦.如果没有安装MongoDB去看我的上一篇博客  MongoDB下载安装与简单增删改查 前 ...

  2. mongoDB 高级查询(一)

    MongoDB,作为NoSql数据库,主要解决的问题不是高性能的并发读写,而是保证海量数据存储的同时,具有良好的查询性能.MongoDB支持的查询语言特别强大,几乎能实现关系型数据库单表查询的大多数功 ...

  3. mongoDB高级查询与索引

    整理来自 https://www.shiyanlou.com/courses/running/78 覆盖索引查询 所有的查询字段都是索引的一部分:所有的查询返回字段在同一个索引中. 由于索引存在于RA ...

  4. mongoDB 高级查询之取模查询$mod

    http://hancang2000.i.sohu.com/blog/view/235140698.htm $mod取模运算 查询age取模10等于0的数据 db.student.find( { ag ...

  5. mongoDB高级查询

    2019独角兽企业重金招聘Python工程师标准>>> 条件查询 1.相等 db.inventory.find({ type: "snacks" }) 2.比较 ...

  6. mongodb 高级查询 统计记录条数

    使用count()方法查询表中的记录条数,例如,下面的命令查询表users的记录数量: db.users.find().count();

  7. Mongodb高级查询Aggregation聚合组件分页

    用Mongo的聚合组件Aggregation要用到两个方法skip和limit.skip设置起点(分页的时候不包含起点,从起点的下一行开始),limit设置条数.如: Aggregation.skip ...

  8. python mongodb分页查询_python数据库分页查询语句

    用django将数据从数据库提出并分页展示 不要笑这个太简单噢~~大神们路过的请自动忽略本文-- '''django有自带的分页功能,我这个是自己做的分页,效果其实是一样的. ''' 要把数据提出并展 ...

  9. MongoDB 教程三: 高级查询

    视频:MongoDB 教程三: 高级查询 MongoDB支持的查询语言非常强大,语法规则类似于面向对象的查询语言,可以实现类似关系数据库单表查询的绝大部分功能,并且由于 MongoDB可以支持复杂的数 ...

最新文章

  1. iOS中都有什么设计模式?各个设计模式的作用 (转载)
  2. 跨地域的VPC私网互通【高速通道案例】
  3. 关于 Unity WebGL 的探索
  4. python自动控制库_一个可以自动化控制鼠标键盘的库:PyAUtoGUI
  5. 再次详解clientHeight、offsetHeight、scrollHeight
  6. python模拟百度搜索点击链接_python采集百度搜索结果带有特定URL的链接代码实例...
  7. 深交所与工信部签署优质中小企业上市培育战略合作协议
  8. UVa 1639 - Candy(数学期望 + 精度处理)
  9. java配置struts2_[Java教程]struts2配置
  10. 2019.04.17 面向对象编程篇207
  11. 计算机软件实习项目四 —— 校园一卡通管理系统 (实验准备) 12-27
  12. 真正菜鸟用教程之WQSG Scrip Export WQSG (脚本导出导入工具,PSP、NDS汉化必备 )
  13. Selenium GUI自动化下载百度图片
  14. outlook服务器上修改文件保存,邮件附件中的文档修改后保存在哪
  15. pgadmin 4 v4.28 keeps loading
  16. 【N32G457 】基于RT-Thread和N32G457 数码管时钟
  17. 利用C#脚本来处理Excel
  18. 在Python中一步一步实现Principal Component Analysis(PCA)
  19. `英语` 2022/8/26
  20. 万字长文剖析Dapp生态,《2019 Dapp行业报告》重磅发布

热门文章

  1. 2009年经典到变态的语录100句
  2. python中reduce函数的运用_Python reduce函数
  3. linux sudo也显示权限不够,重定向时权限不够问题解决
  4. 关于Ubuntu ssh远程连接报错和无法root登录的解决方法
  5. [转载]总结LDO与升压芯片的选型
  6. 自定义控件知识储备-LayoutParams的那些事
  7. 如何编写JavaScript
  8. 电机与拖动基础(上)
  9. C#stream读写
  10. 趣图:等等,我还没备份呢