警惕!mongodb文档数组查询容易犯错

与mongodb的数组查询类似,mongodb针对文档数组查询也值得一读,使用的时候,值得认真思考。

构建测试数据

在inventory集合中,插入5份文档。其中字段instock是文档数组类型的字段,有文档元素构成。文档包括warehouse, qty两个字段。

db.inventory.insertMany( [
   { item: "journal", instock: [ { warehouse: "A", qty: 5 }, { warehouse: "C", qty: 15 } ] },
   { item: "notebook", instock: [ { warehouse: "C", qty: 5 } ] },
   { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 15 } ] },
   { item: "planner", instock: [ { warehouse: "A", qty: 40 }, { warehouse: "B", qty: 5 } ] },
   { item: "postcard", instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);

使用文档作为查询条件

构建本例中的第一个查询语句。查询instock数组中包含文档{warehouse: "A", qty: 5}的结果。

db.inventory.find({"instock": {warehouse: "A", qty: 5}})

mongodb的查询条件当中对字段指定内嵌文档时,只能够返回严格匹配的数据。包括字段顺序,也要保持一致。当交换查询条件中的warehouse和qty字段时,无法返回查询结果。

//无法返回结果
db.inventory.find({"instock": {qty: 5,warehouse: "A"}})

使用查询操作符$elemMatch,mongodb在匹配时,保证字段和值一致即可查询出结果,无需保证字段顺序一致。

//查询出1条文档记录
db.inventory.find({"instock": {$elemMatch: { qty:5, warehouse: "A"}}})

使用查询操作符

除了使用$elemMatch以外,mongodb支持其他查询操作符构建查询条件。

使用点操作符,在查询条件中指定内嵌字段值。下面查询中,当数组instock的元素中,存在qty数量小于等于20的文档,就会被查询出来。用户无需知道文档在instock数组中的排列位置。

db.inventory.find({'instock.qty': {$lte: 20}})

当然,用户也可以基于文档在数组中的位置,构建查询条件。数组中的位置,是从0开始的数组元素索引。下面的查询语句中,instock数组第一个元素qty字段值小于等于20时,文档就会被查询出来。

db.inventory.find({'instock.0.qty': { $lte: 20}})

使用点操作符时,需要将字段名称、点号和数组索引,使用引号包起来。

使用复合查询条件

复合查询条件,确实是mongodb数组查询中最容易出错的地方。每次读到这里,都会让自己警惕一番。

使用$elemMatch构建复合查询

数组查询使用$elemMatch, 符合日常数组查询的使用方式。即要求数组元素,同时满足$elemMatch中列出来的所有查询条件,该文档才会被查询出来。

如下面两个查询语句。

//查询instock数组元素中,包含字段warehouse是'A',同时包含字段qty是5的元素的文档
db.inventory.find({"instock": {$elemMatch: { qty:5, warehouse: "A"}}})
//查询instock数组元素中,qty字段大于10,同时小于等于20的元素所在文档
db.inventory.find({'instock': {$elemMatch: {qty: {$gt:10, $lte:20}}}})

不使用$elemMatch

mongodb数组元素查询,不使用$elemMatch,就要非常小心了,这种方式构建出来的复合查询条件返回结果,与习惯性的做法返回结果有差异。

如下面的查询语句。该语句中,当instock数组中,包含一条qty大于10的数据,同时包含一条qty小于等于20的数据,该文档就会被查询出来了。并不限定于通instock数组中的同一个元素。

db.inventory.find({'instock.qty': {$gt: 10, $lte: 20}})

回到插入inventory集合的5份文档

 { item: "journal", instock: [ { warehouse: "A", qty: 5 }, { warehouse: "C", qty: 15 } ] },
 { item: "notebook", instock: [ { warehouse: "C", qty: 5 } ] },
 { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 15 } ] },
 { item: "planner", instock: [ { warehouse: "A", qty: 40 }, { warehouse: "B", qty: 5 } ] },
 { item: "postcard", instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }

文档{item: "journal"}, instock数组中两个元素中,{warehouse: "C"}的qty是15, 满足大于10,两个元素qty的值,都小于20,所以返回在查询结果中

文档{item: "notebook"}, instock数组中两个元素中,{warehouse: "C"}的qty是5, 满足小于20,但没有大于10的元素,所以不符合查询条件

文档{item: "paper"}, instock数组中两个元素中,{warehouse: "A"}的qty是60, 满足大于10,{warehouse: "B"}的qty是15, 满足小于等于20,所以返回在查询结果中

文档{item: "planner"}, instock数组中两个元素中,{warehouse: "A"}的qty是40, 满足大于10,{warehouse: "B"}的qty是5, 满足小于等于20,所以返回在查询结果中

文档{item: "postcard"}, instock数组中两个元素中,{warehouse: "B"}的qty是15, 满足大于10,{warehouse: "B"}的qty是5, 满足小于等于20,所以返回在查询结果中

由此可以看到,mongodb在查询时,并没有只通过数组中的一个元素值来判断是否符合查询条件,而是只要出现符合查询条件的数据,即使不是同一个数组元素,就会被查询出来。这里就是mongodb数组查询中,非常值得思考和容易出错的地方。

下面的查询语句也是一样,只要数组中出现一个元素满足'instock.qty': 5,同时还存在一个元素满足"instock.warehouse": "A",就会被查询出来。

db.inventory.find({'instock.qty': 5, "instock.warehouse": "A"})

此处在编写代码时,也容易忽略而造成各种错误,值得警惕。