MongoDB学习笔记(一)- 文档查询和投影

文章目录

安装mongodb

我使用的是macOS bigsur,因此这里记录的是在mac下安装mongodb社区版。在mac下最便捷的安装方式就是使用brew来安装,使用以下命令:

  1. 添加官方tap

    1brew tap mongodb/brew
    
  2. 安装mongo全家桶,包括社区版服务端,Shell和数据库相关工具

    1brew install mongodb-community
    

安装完成后,对应的二进制文件和配置文件的位置如下:(Intel处理器)

文件位置
二进制可执行文件/usr/local/Cellar/mongodb-community/5.0.1/bin
配置文件/usr/local/etc/mongod.conf
日志文件/usr/local/var/log/mongodb
数据文件/usr/local/var/mongodb

MongoDB官方提供了以供学习用的示例数据集合,可以从https://atlas-education.s3.amazonaws.com/sampledata.archive下载,关于示例数据集的详细信息,可以参考:The MongoDB Atlas Sample Datasets

以服务的方式启动/停止mongodb

启动mongodb服务:

1brew services start mongodb-community@5.0

服务启动后,可以通过mongo命令连接并进入shell。

停止mongodb服务:

1brew services stop mongodb-community@5.0

mongodb查询操作

这里介绍的关于mongodb的查询操作(MQL)是通过mongosh方法来实现的,这些方法底层是基于JavaScript,但是如果你使用JavaScript来连接那么你应该参考相应语言的包(idiomatic driver )。关于新增、删除和更新的方法可以查看文档:mongosh Methods — MongoDB Manual。这里记录一下查找方法的使用。

方法定义

方法定义:db.collection.find(query, projection)

参数列表如下:

参数参数类型描述
querydocument可选参数。使用查询选择器构建查询条件。如果你希望返回集合里的所有文档,那么忽略这个参数,或者传递一个空的文档({})
projecttiondocument可选参数。对查询结果进行筛选,选择性的返回指定的字段而不是包含文档里的所有字段。如果希望返回文档里的所有字段,则忽略该参数。关于更多细节,请参看:Projection(投影)

构建query的时候,会涉及到字段或者嵌套字段。所谓嵌套字段是指子文档里的字段。指定嵌套的字段的方式有两种:

  • 点标记法:"field.nestedfield": <value>
  • 嵌套的方式:{ field: { nestedfield: <value> } } (**注意:**这种方式和点标记法不一样,这种方式将匹配名称为filed的子文档,子文档只包含一个名为nestedfield的字段)

查询选择器

这部分官网有很好的解释说明,详见:Query and Projection Operators — MongoDB Manual

比较

数学运算符(SQL)MQL运算符语法说明
=$eq{ <field>: { $eq: <value> } } 如果value不是正则表达式,也可以写成{ field: <value> }
>$gt{field: {$gt: value} }
>=$gte{field: {$gte: value} }
in$in{ field: { $in: [<value1>, <value2>, ... <valueN> ] } } 字段的值给定的数组里
<$lt{field: {$lt: value} }
<=$lte{ field: { $lte: value} }
!=$ne{field: {$ne: value} }
not in$nin{ field: { $nin: [ <value1>, <value2> ... <valueN> ]} }字段的值不在给定的数组里

注意: 在进行比较的时候,比较操作符两边的数据需要是相同的BSON类型(Comparison/Sort Order — MongoDB Manual

对于同一个字段的范围的判断,可以使用下面提到的$and逻辑运算,也可以简单的放在一起:

1db.bios.find({birth: {$gt: new Date("1940-01-01"), $lt: new Date("1960-01-01")}})

或者:

1db.bios.find({$and:[{birth:{$gt: new Date("1940-01-01")}},{birth:{$lt: new Date("1960-01-01")}}]})

逻辑运算

MQL运算符语法说明
$and{ $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
$not{ field: { $not: { <operator-expression> } } }注意{ $not: { $gt: 1.99 } }{ $lte: 1.99 }不一样,如果字段不存在,前者会匹配到,但是后者得存在才能匹配到。原因是前者不大于1.99包括小于等于1.99,也包括不存在的,后者明确就是小于等于1.99
$nor{ $nor: [ { <expression1> }, { <expression2> }, ... { <expressionN> } ] }匹配表达式列列表都不满足情况,如果表达式中涉及到的所有字段都不存在,也会匹配出来
$or{ $or: [ { <expression1> }, { <expression2> }, ... , { <expressionN> } ] }匹配表达式列表中的一个或者多个

$not用于修饰数学符号来实现不大于、不小于诸如此类,而$and $not $nor是用于逻辑表达式之间的运算(逻辑表达式由字段和数学表达式构成),这里要区分开。

元素(Element)

MQL运算符语法说明
$exists{ field: { $exists: <boolean> } }匹配某个字段是否存在。如果值是true那么会匹配到字段存在的或者值为null
$type{ field: { $type: <BSON type> } }或者{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }根据字段的类型匹配,可以匹配某种特定类型,或者几种类型中的一种。BSON的支持的类型:$type — MongoDB Manual

评估(Evaluation)

MQL运算符说明
$expr在query中使用复杂表达式来查询文档 $expr — MongoDB Manual
$jsonSchema校验文档的schema
$mod取模运算
$regex使用正则表达式来匹配 $regex — MongoDB Manual
$text使用文本搜索来匹配字段,前提是这个字段上有text索引
$where使用自定义的JS表达式或者方法进行匹配

数组(Array)

MQL运算符说明
$all通过文档数组里的元素进行匹配,数组元素需要满足$all中定义的所有条件。类似$and
$elemMatch通过文档数组里的元素进行匹配,数组元素中的一个或者多个需要满足条件
$size通过文档数组元素的个数进行匹配

Projection(投影)

投影的目的是将query的结果按要求返回,比如需要返回哪些字段、返回几个符合条件的数组元素。注意

  1. 包含或者排除,这二者在投影里是互斥的。即指定了包含的字段,那么结果仅返回指定的字段,如果指定了排除的字段,那么返回的结果不包含被排除的字段
  2. _id是特例,默认是包含在返回结果里,如果不希望结果里返回,是需要在投影里排除掉。_id的排除是可以和其他字段的包含同时存在
Projection描述
<field>: <1 or true>结果中包含某个字段。只要是非0的整型数字,都会被当成true
<field>: <0 or false>结果中排除某个字段
<field>: <aggregation expression>TODO - 待补充

关于对于数组的投影,参考以下操作符:

MQL运算符语法说明
$db.collection.find( { <array>: <condition> ... },{ "<array>.$": 1 } ) db.collection.find( { <array.field>: <condition> ...}, { "<array>.$": 1 } ) 查找条件是基于文档内部的数组元素,通过$投影出符合条件的第一个数组元素
$elemMatch$类似,投影出符合条件的第一个数组元素。$elemMatch是在query的结果上,对数组元组的一个或者多个进行继续过滤:db.collection.find( { query, array: {$elemMatch: {condition ...}} ... }
$meta获取文档相关的元数据,包括textScoreindexKeytextScore和query中的text一起使用,表示文档和query的匹配程度;indexKey顾名思义获取文档对应的非text类型的索引键,主要用于调试目的(??)
$slice返回符合query条件的数组元素的切片。db.collection.find(<query>,{ <arrayField>: { $slice: <number> } });或者db.collection.find(<query>,{ <arrayField>: { $slice: [ <number>, <number> ] } });

查询示例

这个部分的数据来源是官方bios数据集:The bios Example Collection — MongoDB Manual

查询所有文档

find()方法的query部分不传值,或者传递一个空的文档,那么返回的接口就是所有文档。

1db.bios.find()

按照特定的值查询

  • 查找_id为5的文档:

    1db.bios.find({_id: 5})
    
  • 查找name子文档里last的值为"Hopper"的文档:

    1db.bios.find({"name.last": "Hopper"})
    

使用查询选择器

  • 查找_id的值等于5或者等于ObjectId("51e062189c6ae665454e301d")的文档:

    1db.bios.find({_id: {$in: [5, ObjectId("51e062189c6ae665454e301d")]}})
    
  • 查找birth在1950之后的文档:

    1db.bios.find({birth: {$gt: new Date("1950-01-01")}})
    
  • 使用正则表达式查找name子文档下last以N开头的文档:

    1db.bios.find(
    2   { "name.last": { $regex: /^N/ } }
    3)
    

    $regex的语法格式如下:

    1{ <field>: { $regex: /pattern/, $options: '<options>' } }
    2{ <field>: { $regex: 'pattern', $options: '<options>' } }
    3{ <field>: { $regex: /pattern/<options> } }
    

按照值范围查询

查找birth在1940和1960之间的文档:

1db.bios.find({birth: {$gt: new Date("1940-01-01"), $lt: new Date("1960-01-01")}})

根据多个条件查询

查找birth在1920之后,且death为false的文档:

1db.bios.find( {
2   birth: { $gt: new Date('1920-01-01') },
3   death: { $exists: false }
4} )

或者使用$and操作符

1db.bios.find({$and: [
2    {birth: {$gt: new Date("1920-01-01")}},
3    {death: {$exists: false}}
4]})

精确匹配子文档

所谓精确匹配是指要求子文档的字段的个数以及字段顺序与给定的查询条件中的子文档一致。下面的例子中,要求匹配name子文档的first为Yukihiro,last为Matsumoto,文档中这两个字段的顺序也是要匹配上

1db.bios.find(
2    { name: { first: "Yukihiro", last: "Matsumoto" } }
3)

下面的两种情况无法匹配成功:

 1{
 2   "first": "Yukihiro",
 3   "aka": "Matz",
 4   "last": "Matsumoto"
 5}
 6
 7{
 8   "last": "Matsumoto",
 9   "first": "Yukihiro"
10}

匹配子文档的字段

使用点标记法匹配子文档字段。例如查找name子文档下first为Yukihiro且last为Matsumoto的文档

1db.bios.find(
2   {
3     "name.first": "Yukihiro",
4     "name.last": "Matsumoto"
5   }
6)

根据数组元素(数组元素类型为简单类型)来查找

  • 查找contribs数组里含有UNIX元素的文档(至少有一个元素符合即可):

    1db.bios.find({contribs: "UNIX"})
    
  • 查找contribs数组里含有ALGOL或者Lisp元素的文档(至少有一个元素在$in列表里):

    1db.bios.find({contribs: {$in: ["ALGOL", "Lisp"]}})
    
  • 查找contribs数组里含有ALGOLLisp元素的文档(至少包含$all里定义的所有元素):

    1db.bios.find({contribs: {$all: ["ALGOL", "Lisp"]}})
    
  • 查找contribs数组大小为4的文档:

    1db.bios.find({contribs: {$size: 4}})
    

根据数组元素(数组元素类型为文档类型)来查找

  • 查找awards数组下的子文档中的award的值为Turing Award的文档:

    1db.bios.find(
    2   { "awards.award": "Turing Award" }
    3)
    
  • 查找awards数组元素中award的值为Turing Awardyear(这里必须要在一个数组元素同时满足)大于1999的文档:

    1db.bios.find(
    2   { awards: { $elemMatch: { award: "Turing Award", year: { $gt: 2000 } } } }
    3)
    

    上面的查询可以匹配:

     1"awards": [
     2      {
     3        "award": "Rosing Prize",
     4        "year": 1999,
     5        "by": "Norwegian Data Association"
     6      },
     7      {
     8        "award": "Turing Award",
     9        "year": 2001,
    10        "by": "ACM"
    11      },
    12      {
    13        "award": "IEEE John von Neumann Medal",
    14        "year": 2001,
    15        "by": "IEEE"
    16      }
    17    ]
    

    但是无法匹配:

     1"awards": [
     2      {
     3        "award": "Turing Award",
     4        "year": 1983,
     5        "by": "ACM"
     6      },
     7      {
     8        "award": "National Medal of Technology",
     9        "year": 1998,
    10        "by": "United States"
    11      },
    12      {
    13        "award": "Japan Prize",
    14        "year": 2011,
    15        "by": "The Japan Prize Foundation"
    16      }
    17    ]
    

    如果这里不使用elemMatch而是使用下面的方式,结果返回的是awards元素里包含award为”Turing Award“和year大于1975的元素。这两个条件是要求awards下的所有元素里,满足这两个条件即可。这两个条件是限制在awards上,不是限制在单一的数组元素上,因此上面无法匹配的例子也可以被下面的查询匹配。

    1db.bios.find(
    2   {
    3        "awards.award": "Turing Award" ,
    4        "awards.year": {$gt: 1975}
    5   }
    6)
    

投影示例

指定要包含的字段

查找bios集合里所有文档并返回文档的namecontribs字段:

1db.bios.find( { }, { name: 1, contribs: 1 } )

指定要排除的字段

查找bios集合里contribs为OOP的集合,返回的时候排除掉name.firstbirth字段:

1db.bios.find(
2   { contribs: 'OOP' },
3   { 'name.first': 0, birth: 0 }
4)

显式排除_id字段

查找bios集合里所有文档并返回文档的namecontribs字段但不包括_id

1db.bios.find(
2   { },
3   { name: 1, contribs: 1, _id: 0 }
4)

对子文档和数组投影

查找bios集合里的所有文档并返回name.last,以及contribs数组里的前两个元素

1db.bios.find(
2    {},
3    {_id: 0, 'name.last': 1, contribs: {$slice: 2}}
4)