使用$ elemMatch时如何投影超过第一个子文档

时间:2017-10-01 15:27:31

标签: java mongodb aggregate projection mongodb-java

我有一个包含以下形式文件的集合:

{
    "_id" : { "$oid" : "67bg............"},
    "ID"  : "xxxxxxxx",
    "senses" : [
        {
            "word"   : "hello",
            "lang"   : "EN",
            "source" : "EN_DICTIONARY"
        },
        {
            "word"   : "coche",
            "lang"   : "ES",
            "source" : "ES_DICTIONARY"
        },
        {
            "word"   : "bye",
            "lang"   : "EN",
            "source" : "EN_DICTIONARY"
        }
    ]
}

我想找到与lang=Xsource=Y至少匹配一个意义的所有文档,并返回匹配的文档,只返回匹配senses和{{lang=X的{​​{1}} 1}}。

我试过了:

source=Y

我的查询适用于匹配文档,但不适用于投影。以上面的文档为例,如果我运行我的查询,我得到这个结果:

DBObject sensesQuery = new BasicDBObject();
sensesQuery.put("lang", "EN");
sensesQuery.put("source", "EN_DICTIONARY");
DBObject matchQuery = new BasicDBObject("$elemMatch",sensesQuery);

DBObject fields = new BasicDBOject();
fields.put("senses",matchQuery);

DBObject projection = new BasicDBObject();
projection.put("ID",1)
projection.put("senses",matchQuery);
DBCursor cursor = collection.find(fields,projection)

while(cursor.hasNext()) {
    ...
}

但我想要这个:

{
    "_id" : { "$oid" : "67bg............"},
    "ID"  : "xxxxxxxx",
    "senses" : [
        {
            "word"   : "hello",
            "lang"   : "EN",
            "source" : "EN_DICTIONARY"
        }
    ]
}

我读过有关聚合但我不明白如何在MongoDB Java驱动程序中使用它。

由于

1 个答案:

答案 0 :(得分:1)

您在投影上使用$elemMatch运算符以及过滤器。

来自the docs

  

$elemMatch运算符将查询结果中字段的内容限制为仅包含与$elemMatch条件匹配的第一个元素。

因此,您看到的行为是elemMatch-in-a-projection的预期行为。

如果要在符合过滤条件的文档中的senses数组中投影所有子文档,则可以使用此文件:

projection.put("senses", 1);

但是,如果您只想投影那些符合您的过滤条件的子文档,那么$elemMatch将不适合您,因为它只返回匹配$elemMatch条件的第一个元素。您可以选择使用聚合框架,例如:

db.collection.aggregate([
  // matches documents with a senses sub document having the given lang and source values
  {$match: {'senses.lang': 'EN', 'senses.source': 'EN_DICTIONARY'}},

  // projects on the senses sub document and filters the output to only return sub 
  // documents having the given lang and source values
  {$project: {
      senses: {
        $filter: {
            input: "$senses",
            as: "sense",
            cond: { $eq: [ "$$sense.lang", 'EN' ], $eq: [ "$$sense.source", 'EN_DICTIONARY' ] }
          }
        }
      }
  }
])

这是使用MongoDB Java驱动程序的聚合调用:

Document filter = new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY");

DBObject filterExpression = new BasicDBObject();
filterExpression.put("input", "$senses");
filterExpression.put("as", "sense");
filterExpression.put("cond", new BasicDBObject("$and", Arrays.<Object>asList(
        new BasicDBObject("$eq", Arrays.<Object>asList("$$sense.lang", "EN")),
        new BasicDBObject("$eq", Arrays.<Object>asList("$$sense.source", "EN_DICTIONARY")))
));

BasicDBObject projectionFilter = new BasicDBObject("$filter", filterExpression);

AggregateIterable<Document> documents = collection.aggregate(Arrays.asList(
        new Document("$match", filter),
        new Document("$project", new Document("senses", projectionFilter))));

for (Document document : documents) {
    logger.info("{}", document.toJson());
}

结果输出为:

2017-10-01 17:15:39 [main] INFO  c.s.mongo.MongoClientTest - { "_id" : { "$oid" : "59d10cdfc26584cd8b7a0d3b" }, "senses" : [{ "word" : "hello", "lang" : "EN", "source" : "EN_DICTIONARY" }, { "word" : "bye", "lang" : "EN", "source" : "EN_DICTIONARY" }] }

更新1 :在此评论之后:

  

经过长时间的测试,试图理解为什么查询速度慢,我注意到“$ match”参数不起作用,查询应该只选择至少有一个含有source = Y和lang的意义的记录= X并投影它们,但查询也返回带有senses = []

的文档

此过滤器:new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY")将不匹配没有senses属性的文档,也不会匹配具有空senses属性的文档。为了验证这一点,我将以下文档添加到我自己的集合中:

{
    "_id" : ObjectId("59d72a24c26584cd8b7b70a5"),
    "ID" : "yyyyyyyy"
}

{
    "_id" : ObjectId("59d72a3ac26584cd8b7b70ae"),
    "ID" : "zzzzzzzzz",
    "senses" : []
}

重新运行上面的代码,我仍然得到了理想的结果。

我怀疑您的上述代码不起作用的声明是假阴性或您查询的文件与我一直使用的样本不同。

为了帮助您自己诊断此问题,您可以......

  • 与其他运营商一起玩,例如$match阶段在使用和不使用$exists运算符时的行为相同:

    new Document("senses", new BasicDBObject("$exists", true))
            .append("senses.lang", new BasicDBObject("$eq", "EN"))
            .append("senses.source", new BasicDBObject("$eq", "EN_DICTIONARY"))
    
  • 删除$project阶段以确切了解$match阶段产生的内容。