前言
此篇博文是 Mongdb 基础系列之一;
本文为作者的原创作品,转载需注明出处;
简介
本文主要是增对特定语言如何执行插入操作的讲解;官网上对各个热门语言如何对 MongoDB 执行 query 操作分别作了比较详细的描述;这里呢,因为 mongo shell 是在平时调试、检查、运维最常用的操作,所以,笔者这里重点考察如何通过 mongo shell 来进行 query 操作;要注意的是,Mongo Shell 是通过 javascript 实现的交互式应用;
在讲解 query 操作以前,假设,我们有如下这些测试用例,在各个章节没有特别指明测试数据的前提下,将会使用下面的测试数据;
1 | db.inventory.insertMany([ |
查询基础
Select All Documents in a Collection
1 | > db.inventory.find( {} ) |
等价于 SQL
1 | SELECT * FROM inventory |
Specify Equality Condition
使用<field>:<value>
来指定相等表达式,格式如下,Query Filter Documents
1 | { <field1>: <value1>, ... } |
在 invertory 中查询 status 为 D 的所有 documents,
1 | db.inventory.find( { status: "D" } ) |
等价于执行 SQL
1 | SELECT * FROM inventory WHERE status = "D" |
Specify Conditions Using Query Operators
在查询的时候,可以使用 query operatorss 来指定 conditions,
1 | { <field1>: { <operator1>: <value1> }, ... } |
比如说,我们要查询 status 是 “A” 或者 “D” 中的 documents,使用$in
操作符;
1 | db.inventory.find( { status: { $in: [ "A", "D" ] } } ) |
等价于执行如下的 SQL
1 | SELECT * FROM inventory WHERE status in ("A", "D") |
Specify AND Conditions
1 | > db.inventory.find( { status: "A", qty: { $lt: 30 } } ) |
上面的查询试图找到 inventory 中 status 为 A 且 qty 字段值小于($lt
) 30 的 documents;注意,MongoDB 查询过程中,在没有其它限定条件,比如$or
的情况下,两个相邻的条件自动的被翻译为 AND 操作;
等价于执行 SQL,
1 | SELECT * FROM inventory WHERE status = "A" AND qty < 30 |
Specify OR Conditions
1 | > db.inventory.find( { $or: [ { status: "A" }, { qty: { $lt: 30 } } ] } ) |
与 Specify AND Conditions 稍有不同,通过$or
操作符,通过格式 $or: [ { ... }, { ... } ]
来通过 OR 连接两个不同的查询条件,
等价于执行 SQL,
1 | SELECT * FROM inventory WHERE status = "A" OR qty < 30 |
Specify AND as well as OR Conditions
1 | db.inventory.find( { |
相当于执行 SQL
1 | SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%") |
查询(单个)嵌入式文档
内嵌文档完整匹配
比如,我们想对嵌入式文档{ h: 14, w: 21, uom: "cm" }
做全值匹配,使用下面这个方式即可,
1 | > db.inventory.find( { size: { h: 14, w: 21, uom: "cm" } } ) |
表示是对 size 字段进行全值匹配;特别需要注意
的是,匹配嵌入式文档的字段名的顺序不能颠倒,
1 | db.inventory.find( { size: { w: 21, h: 14, uom: "cm" } } ) |
如果是像上述这样进行查询,是查询不到任何结果的;
最后,需要记忆
的是,完整匹配内嵌文档的时候,键就是内嵌文档在外部文档中所对应的字段名既size
;
匹配内嵌文档字段
在内嵌文档字段上使用相等匹配
1 | > db.inventory.find( { "size.uom": "in" } ) |
这样,我们在( size )内嵌文档的 uom 字段上使用了相等比较,返回其值等于 _in_ 的所有内嵌文档;
最后,需要记忆
的是,匹配内嵌文档字段的时候,键对应的是内嵌文档中的字段,用size.uom
表示;
在内嵌文档字段上使用查询操作符
使用查询操作符的标准格式为,
1 | { <field1>: { <operator1>: <value1> }, ... } |
下面我们试图查询( size )嵌入文档中高度小于 15 的所有内嵌文档,
1 | > db.inventory.find( { "size.h": { $lt: 15 } } ) |
使用 AND 条件
下面这个例子,我们通过AND
方式既查询内嵌文档的字段并且同时查询 document 中的信息,
1 | > db.inventory.find( { "size.h": { $lt: 15 }, "size.uom": "in", status: "D" } ) |
可见,可以同时结合内嵌文档和外部文档同时进行查询;
对文档中的 Array 进行查询
测试数据准备
假设我们有下面这些测试数据,该测试数据将会作用到下面的本小节相关的所有内容;
1 | db.inventory.insertMany([ |
使用队列进行查询
注意
,这种查找方式需要使用[...]
方括号的方式来表示输入的查询条件是一个队列;
全匹配
1 | > db.inventory.find( { tags: ["red", "blank"] } ) |
上面这个查询将试图精确的匹配 Array tags ;要能精确全部匹配,注意两点,查询参数值的顺序且完全包含所有相关元素;全匹配后将会匹配并输出如下的结果,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9bb"), "item" : "notebook", "qty" : 50, "tags" : [ "red", "blank" ], "dim_cm" : [ 14, 21 ] } |
部分匹配(包含关系)
另外,如果只要包含了 read 和 blank 两个元素的队列都需要被匹配,并且不关心其顺序,那么我们可以使用 $all 操作符;
1 | > db.inventory.find( { tags: { $all: ["red", "blank"] } } ) |
将会输出如下结果,凡是包含了 red 和 blank 元素的相关 Array 都会返回;
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9ba"), "item" : "journal", "qty" : 25, "tags" : [ "blank", "red" ], "dim_cm" : [ 14, 21 ] } |
使用普通值进行查询
注意
,这种查询方式与通过队列进行查询的方式不同,各个查询条件对应的就是一个普通的值而非一个队列;
包含了某一个元素
通过 Array 中的某一个元素进行查找,使用如下的格式来进行查找,其中 <value> 是 Array 中的一个元素;
1 | { <field>: <value> } |
来看一个例子,查找 tags 中包含了 red 的队列
1 | > db.inventory.find( { tags: "red" } ) |
将会输出所有 tags 队列中包含了 red 元素的 documents,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9ba"), "item" : "journal", "qty" : 25, "tags" : [ "blank", "red" ], "dim_cm" : [ 14, 21 ] } |
使用查询操作符
使用单个查询操作符
使用查询操作符来对 Array 进行查找的标准格式如下,
1 | { <array field>: { <operator1>: <value1>, ... } } |
比如,我们试图找到 dim_cm array 中至少包含一个大于 25 的元素的 documents,
1 | > db.inventory.find( { dim_cm: { $gt: 25 } } ) |
输出结果,这样便输出了 dim_cm 队列中包含了大于 25 元素的 documents,只有一个满足条件,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9bd"), "item" : "planner", "qty" : 75, "tags" : [ "blank", "red" ], "dim_cm" : [ 22.85, 30 ] } |
使用多个查询操作符
将过多个滤条件以 OR 的方式匹配队列元素
该条件在官网上称作符合过滤条件( Compound Filter Condition );
来看一个例子,下面的例子使用了复合过滤条件( Compound Filter Conditions ),
1 | > db.inventory.find( { dim_cm: { $gt: 15, $lt: 20 } } ) |
输出结果,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9ba"), "item" : "journal", "qty" : 25, "tags" : [ "blank", "red" ], "dim_cm" : [ 14, 21 ] } |
可以看到,复合过滤条件其实执行的是OR
的关系查询,只要队列元素中某一个元素是大于 15 或者 OR
是小于 20 的 documents,都会被匹配并被作为结果输出;也就是说,这种查询方式是,只要队列中有某一个元素满足其中的一个条件
既算是该 document 匹配了;
将多个过滤条件以 AND 的方式匹配队列元素
这里使用到了操作符$elemMatch
,它的查询方式是,只要队列中有某一个元素同时满足所有的条件
既算是该 document 匹配了;来看一个例子,
1 | > db.inventory.find( { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } ) |
输出结果,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9bd"), "item" : "planner", "qty" : 75, "tags" : [ "blank", "red" ], "dim_cm" : [ 22.85, 30 ] } |
可以看到,dim_cm 队列中的第一个元素 22.85 同时满足了查询条件{ $gt: 22, $lt: 30 }
,所以,该 document 将作为结果输出;
根据数组坐标定位元素进行查询
1 | > db.inventory.find( { "dim_cm.1": { $gt: 25 } } ) |
该查询结果返回,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9bd"), "item" : "planner", "qty" : 75, "tags" : [ "blank", "red" ], "dim_cm" : [ 22.85, 30 ] } |
上面的查询通过 <field> 的值dim_cm.1
指定了将查询操作符 $gt 作用在 dim_cm 队列中的第二个元素上进行查询;注意,坐标是从 0 开始;
通过队列的长度进行匹配查询
1 | > db.inventory.find( { "tags": { $size: 3 } } ) |
执行结果,
1 | { "_id" : ObjectId("5976a8dfcb568ef80824a9bc"), "item" : "paper", "qty" : 100, "tags" : [ "red", "blank", "plain" ], "dim_cm" : [ 14, 21 ] } |
可以看到,当且仅当 tags 队列中有三个元素的 elements 才会返回;注意,这里是通过 $size 操作符来指定该查询动作的;
查询(多个)嵌入式文档
这里的多个
实际上指的是一个 document 的字段包含多个嵌入式的文档 documents;
后记,要特别特别注意的是,这里所谓的多个嵌入式文档在数据结构上仍然是一个数组;
测试数据准备
1 | db.inventory.insertMany( [ |
可以看到,instock 是由多个嵌入式 document 所组成;
全匹配
1 | > db.inventory.find( { "instock": { warehouse: "A", qty: 5 } } ) |
返回的查询结果,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
可见,只要多个内嵌 documents 中只要有一个完全匹配了上述的查询条件,则将会返回该 document;
这个非常类似于前面介绍的内嵌文档完整匹配的内容,只是这里针对一个字段,内嵌的是多个 documents 的情况;
要特别注意的是
,要完全匹配某一个内嵌文档,顺序不能乱,元素不能少;如果是像下面这样去查询,是不会返回任何结果的,
1 | > db.inventory.find( { "instock": { qty: 5, warehouse: "A" } } ) |
使用单个条件
使用 document 下标进行查询匹配
对多个嵌入文档进行查询的时候,可以通过指定下标来表示对第几个 document 进行匹配查询;如下,
1 | > db.inventory.find( { 'instock.0.qty': { $lte: 20 } } ) |
上面这个查询的条件是,匹配instock
的第一个嵌入文档,该文档中的 qty 字段值小于等于 20;注意,坐标是从0
开始;
输出结果为,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
匹配所有嵌入文档
很多时候,我们并不非常清楚自己需要查询的 document 的坐标位置,这个时候,我们可以针对所有的嵌入文档进行查询;如下,
1 | > db.inventory.find( { 'instock.qty': { $lte: 20 } } ) |
上述查询就没有指定坐标值,则表示对 instock 字段中的所有内嵌文档进行查询,得到如下结果,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
使用多个条件
将多个过滤条件以 OR 的方式匹配 Document
这个和之前讨论到的将过多个滤条件以 OR 的方式匹配队列元素是类同的,只是前面作用到的是队列中的每个元素,而这里作用到的是多个内嵌文档中的一个文档;
1 | > db.inventory.find( { "instock.qty": { $gt: 10, $lte: 20 } } ) |
输出结果,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
可以看到,只要任意一个内嵌文档满足了两个条件中的一个,该外部 document 将作为结果输出;在来看一个例子,
1 | > db.inventory.find( { "instock.qty": 5, "instock.warehouse": "A" } ) |
输出,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
可以看到,只要任何一个内嵌文档满足了其中的一个条件,那么该外部 document 将会作为结果输出;
将多个过滤条件以 AND 的方式匹配 Document
好的,前文讲述了如何将多个条件以 OR 的方式来匹配 document,那么这里,我们看看如何将多个条件以 AND 的方式来作用到 Document;和将多个过滤条件以 AND 的方式匹配队列元素类似,这里也是通过使用$elemMatch
操作符,来实现这种查询,来看这样一个例子,
1 | > db.inventory.find( { "instock": { $elemMatch: { qty: 5, warehouse: "A" } } } ) |
输出结果如下,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
可以看到,必须至少有一个内嵌 document 同时满足这两个查询条件,结果才会输出;同理,在看下面这个例子,
1 | db.inventory.find( { "instock": { $elemMatch: { qty: { $gt: 10, $lte: 20 } } } } ) |
输出如下结果,
1 | { "_id" : ObjectId("5976da82cb568ef80824a9bf"), "item" : "journal", "instock" : [ { "warehouse" : "A", "qty" : 5 }, { "warehouse" : "C", "qty" : 15 } ] } |
限定返回字段
测试数据
假设,我们有如下的测试数据,
1 | db.inventory.insertMany( [ |
返回所有字段
默认情况下,没有对返回结果的字段做限制的前提下,返回结果将会返回所有的字段,
1 | > db.inventory.find( { status: "A" } ) |
等价于执行 SQL,
1 | SELECT * from inventory WHERE status = "A" |
返回指定的字段集
通过将<field>
设置为1
将字段投影( projection )到输出结果中;看下面这个例子,
1 | > db.inventory.find( { status: "A" }, { item: 1, status: 1 } ) |
输出结果,
1 | { "_id" : ObjectId("5976f17ecb568ef80824a9c4"), "item" : "journal", "status" : "A" } |
可以看到,返回的结果集中包含了我们所设置的字段,item 和 status 以及一个默认输出的字段 _id;
相当于执行下面的这段 SQL,
1 | SELECT _id, item, status from inventory WHERE status = "A" |
如何不输出 _id 字段
1 | > db.inventory.find( { status: "A" }, { item: 1, status: 1, _id: 0 } ) |
这样,结果集中就不会再包含 _id 字段再包含进去了;相当于执行 SQL,
1 | SELECT item, status from inventory WHERE status = "A" |
指定不返回的字段集
1 | > db.inventory.find( { status: "A" }, { status: 0, instock: 0 } ) |
该查询将会返回所有的结果字段除了 status 和 instock;
需要注意
的是,除了 _id 这种特殊情况,你不能同时指定 inclusiong 和 exclusion 的语句;
限定(单个)嵌入文档的返回字段集
这里的单个
是指,某个文档字段包含只包含一个内嵌的 document;
返回嵌入文档中的指定字段集
1 | db.inventory.find( |
返回,
1 | { "_id" : ObjectId("5976f17ecb568ef80824a9c4"), "item" : "journal", "status" : "A", "size" : { "uom" : "cm" } } |
正如结果中所看到的那样,针对 size 中的嵌入式文档,只会返回 uom 字段的结果;
不返回嵌入文档的某些字段集
不返回嵌入式文档的某些字段,但是其余的字段全部返回;
1 | db.inventory.find( |
返回,
1 | { "_id" : ObjectId("5976f17ecb568ef80824a9c4"), "item" : "journal", "status" : "A", "size" : { "h" : 14, "w" : 21 }, "instock" : [ { "warehouse" : "A", "qty" : 5 } ] } |
可以看到,返回的嵌入式文档中,除了 uom 字段值没有返回以外,其它的字段值都返回了;
限定(多个)嵌入文档的返回字段集
这里的多个
是指,某个文档字段包含多个内嵌的 documents;
限定返回字段集
1 | > db.inventory.find( { status: "A" }, { item: 1, status: 1, "instock.qty": 1 } ) |
instock 字段可以包含多个内嵌 documents,通过上述的查询,会同时返回所有与之匹配的内嵌文档结果,
1 | { "_id" : ObjectId("5976f17ecb568ef80824a9c4"), "item" : "journal", "status" : "A", "instock" : [ { "qty" : 5 } ] } |
通过限定操作符来限定返回字段集
MongoDB 只提供了 $elemMatch, $slice, and $ 这三个操作符来限定返回字段集;
以 $slice 为例,下面的这个例子只会返回 instock 内嵌文档集合中的最后一个元素;
1 | db.inventory.find( { status: "A" }, { name: 1, status: 1, instock: { $slice: -1 } } ) |
返回,
1 | { "_id" : ObjectId("5976f17ecb568ef80824a9c4"), "status" : "A", "instock" : [ { "warehouse" : "A", "qty" : 5 } ] } |
查询空值或缺失字段
测试数据
1 | db.inventory.insertMany([ |
匹配 NULL
使用{ item : null }
来匹配 documents,将会匹配两种情况,1、包含 item 字段且 item 值为 null;2、不包含 item 字段;
1 | > db.inventory.find( { item: null } ) |
返回,
1 | { "_id" : 1, "item" : null } |
另外,还可以使用 BSON Type 的方式来进行匹配,使用 { item : { $type: 10 } }
,其中,{ $type: 10 }
表示的是 BSON Type Null;使用 BSON Type Null 来进行查询,只会匹配某个文档,包含此 item 字段,且其值为 null;
1 | > db.inventory.find( { item : { $type: 10 } } ) |
返回,可以看到,这次只会返回包含 item 字段的值;
1 | { "_id" : 1, "item" : null } |
相关内容参考$type;
匹配字段是否存在
1 | > db.inventory.find( { item : { $exists: false } } ) |
通过{ item : { $exists: false } }
语句表示查询和返回不存在 item 字段的结果集;上述查询将会返回,
1 | { "_id" : 2 } |
相关内容参考$exits