使用 $elemMatch 时如何投影超过第一个子文档
How to project more than the first sub-document when using $elemMatch
我收集了以下形式的文档:
{
"_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=X
和 source=Y
和 return 至少匹配一种意义的文档,只有那些 senses
匹配 lang=X
和 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"
}
]
}
但我想要这个 :
{
"_id" : { "$oid" : "67bg............"},
"ID" : "xxxxxxxx",
"senses" : [
{
"word" : "hello",
"lang" : "EN",
"source" : "EN_DICTIONARY"
},
{
"word" : "bye",
"lang" : "EN",
"source" : "EN_DICTIONARY"
}
]
}
我阅读了有关聚合的内容,但我不明白如何在 MongoDB Java 驱动程序中使用它。
谢谢
您正在对投影和过滤器使用 $elemMatch
运算符。
来自the docs
The $elemMatch
operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch
condition.
因此,您看到的 行为是 elemMatch-in-a-projection 的预期行为。
如果你想投影文档中 senses
数组中符合过滤条件的所有子文档,那么你可以使用这个:
projection.put("senses", 1);
但是,如果您只想投影那些符合过滤条件的子文档,那么 $elemMatch
将不适合您,因为它只会 returns 第一个与 [=16= 匹配的元素] 健康)状况。您的替代方案是使用聚合框架,例如:
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:关注此评论:
After a long period of testing, trying to understand why the query was slow, I noticed that the "$match" parameter does not work, the query should select only records that have at least one sense with source = Y AND lang = X and project them , but the query also returns me documents with senses = []
此过滤器:new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY")
不会匹配没有 senses
属性的文档,也不会匹配具有空 senses
属性的文档。为了验证这一点,我将以下文档添加到我自己的 collection:
{
"_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
阶段产生的内容。
我收集了以下形式的文档:
{
"_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=X
和 source=Y
和 return 至少匹配一种意义的文档,只有那些 senses
匹配 lang=X
和 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"
}
]
}
但我想要这个 :
{
"_id" : { "$oid" : "67bg............"},
"ID" : "xxxxxxxx",
"senses" : [
{
"word" : "hello",
"lang" : "EN",
"source" : "EN_DICTIONARY"
},
{
"word" : "bye",
"lang" : "EN",
"source" : "EN_DICTIONARY"
}
]
}
我阅读了有关聚合的内容,但我不明白如何在 MongoDB Java 驱动程序中使用它。
谢谢
您正在对投影和过滤器使用 $elemMatch
运算符。
来自the docs
The
$elemMatch
operator limits the contents of an field from the query results to contain only the first element matching the$elemMatch
condition.
因此,您看到的 行为是 elemMatch-in-a-projection 的预期行为。
如果你想投影文档中 senses
数组中符合过滤条件的所有子文档,那么你可以使用这个:
projection.put("senses", 1);
但是,如果您只想投影那些符合过滤条件的子文档,那么 $elemMatch
将不适合您,因为它只会 returns 第一个与 [=16= 匹配的元素] 健康)状况。您的替代方案是使用聚合框架,例如:
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:关注此评论:
After a long period of testing, trying to understand why the query was slow, I noticed that the "$match" parameter does not work, the query should select only records that have at least one sense with source = Y AND lang = X and project them , but the query also returns me documents with senses = []
此过滤器:new Document("senses.lang", "EN").append("senses.source", "EN_DICTIONARY")
不会匹配没有 senses
属性的文档,也不会匹配具有空 senses
属性的文档。为了验证这一点,我将以下文档添加到我自己的 collection:
{
"_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
阶段产生的内容。