关于MongoDB中对于Collection中的Array数组的注意事项

关于MongoDB中对于Collection中的Array数组的注意事项

前两天在工作中遇到一个对mongoDB的Collection中Array数组查询的问题,百思不得其解之后豁然开朗,今天给大家分享一下。

当时遇到的问题是,我现在有两个文档,其结构分别为:

{
"_id" : ObjectId("58e88fa90cf2b631bab2f0d8"),
"title" : "A",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status":1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "travelPlace",
        "status":1
    }
],
"status" : 1
}
{
"_id" : ObjectId("58e89caaf323560340686c31"),
"title" : "B",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status":1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "articlePlace",
        "status":1
    }
],
"status" : 1
}

文档A和文档B中都有一个places数组,其中placeId为58de05f10cf2c09e03c3542b在两个文档中都有,但是这两个的sourceType又分别不同,现在,我需要将placeId为58de05f10cf2c09e03c3542b,同时对应的sourceType为travelPlace的文档A给筛选出来。

遇到这个问题的时候,首先,我的查询语句是这个样子的:

db.getCollection('test').find({"places.placeId":"58de05f10cf2c09e03c3542b","places.sourceType":"travelPlace"})  

结果如下所示:

{
"_id" : ObjectId("58e88fa90cf2b631bab2f0d8"),
"title" : "A",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status":1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "travelPlace",
        "status":1
    }
],
"status" : 1
}

看起来应该是正确的,但是当我变更一下query语句之后:

db.getCollection('test').find({"places.placeId":"58de05f10cf2c09e03c3542b","places.sourceType":"articlePlace"}) 

结果出来是:

{
"_id" : ObjectId("58e88fa90cf2b631bab2f0d8"),
"title" : "A",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status":1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "travelPlace",
        "status":1
    }
],
"status" : 1
}

{
"_id" : ObjectId("58e89caaf323560340686c31"),
"title" : "B",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status":1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "articlePlace",
        "status":1
    }
],
"status" : 1
}

两个文档都被查询出来了!what?为什么会命中两个记录?

我们来分析一下查询语句:

db.getCollection('test').find({"places.placeId":"58de05f10cf2c09e03c3542b","places.sourceType":"articlePlace"}) 

这条语句有两个条件:

  • 查询出所有places.placeId为58de05f10cf2c09e03c3542b的文档;
  • 查询出所有places.sourceType为articlePlace的文档

这两个条件组合在一起之后,其执行过程是:

  • 首先找出所有places.placeId等于58de05f10cf2c09e03c3542b的文档,命中文档A和文档B,原因是文档A中places子集中有一个placeId为58de05f10cf2c09e03c3542b,sourceType为travelPlace的文档,所以命中文档A,文档B中同样存在一个placeId为58de05f10cf2c09e03c3542b的文档子集;
  • 然后找出所有places.sourceType等于articlePlace的文档,其命中结果也是文档A和文档B,它的逻辑是文档A中有一个placeId为58d24465f323560340686c1a,sourceType为articlePlace的子集,文档B中有两个sourceType为articlePlace,placeId不同的文档子集;
  • 将两个结果合并取交集;

我们可以看到,此时query语句对于places数组文档的操作粒度是基于整个文档的,对两个约束条件的交集,而不是对每一个文档的places子文档集的交集;

在此我们可以打一个比方:我有一百块钱预算,我先去便利店买个一百的商品A、A,然后我回家了,第二次我又去便利店买了一百的东西B、B,然后我又回家了,到家之后我把两次购买的商品放到一起,发现总商品价值两百元,超支了!我原有的计划是在AB两种商品中挑选一种,然后只花一百块钱。结果我买了两次,然后每次都买了一百,结果就错了。

既然发现了问题,下面我们要解决的就是如何在一次约束条件中完成对子文档列表的整个查询约束,我把原有的查询条件变更为下面这种形式:

db.getCollection('test').find({"places":{"placeId":"58de05f10cf2c09e03c3542b","sourceType":"articlePlace"}})

现在的条件是:对places进行一次查询,约束条件为placeId等于58de05f10cf2c09e03c3542b,sourceType等于articlePlace;

执行之后发现,没有返回任何结果!Why?查看官方文档中对关于”Query a Array”的描述中有这样一段话:

Match an Array

To specify equality condition on an array, use the query document { <field>: <value> } where <value> is the exact array to match, including the order of the elements. 

原来通过这种{:}的操作需要后面的value是和array中子集完全匹配,可是对于Array中的子集,实际项目中可能还会有很多其他的内容,这些可能各不相同,如果要精确查询,最后只能得到一个准确的值,不符合我们的需要。我们需要对一个array子集进行多重符合条件的查询,但是又需要忽略那些不必要的元素,最后没办法,只能祭出杀手锏—-看官方API。

Query for an Array Element that Meets Multiple Criteria

Use $elemMatch operator to specify multiple criteria on the elements of an array such that at least one array element satisfies all the specified criteria.

原来当我们需要对array子集进行多重符合查询语句时,需要使用$elemMacth查询条件,这样,我们的Query语句最终变为这样:

db.getCollection('test').find({"places":{$elemMatch:{"placeId":"58de05f10cf2c09e03c3542b","sourceType":"articlePlace"}}})

执行后结果:

{
"_id" : ObjectId("58e89caaf323560340686c31"),
"title" : "B",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status" : 1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "articlePlace",
        "status" : 1
    }
],
"status" : 1
}

get!我们再换一下sourceType:

db.getCollection('test').find({"places":{$elemMatch:{"placeId":"58de05f10cf2c09e03c3542b","sourceType":"travelPlace"}}})

执行结果:

{
"_id" : ObjectId("58e88fa90cf2b631bab2f0d8"),
"title" : "A",
"places" : [ 
    {
        "placeId" : "58d24465f323560340686c1a",
        "sourceType" : "articlePlace",
        "status" : 1
    }, 
    {
        "placeId" : "58de05f10cf2c09e03c3542b",
        "sourceType" : "travelPlace",
        "status" : 1
    }
],
"status" : 1
}

如果需要对Collection的Array子集进行整体性地复合查询,需要使用$elemMatch,如果需要精确查询,使用 { : } 格式。

希望能够对各位有所帮助,谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值