我的目标是返回多个 QuestionElements,其中 QuestionElements 元标记条目等于我的搜索。例如。如果一个metaTag元素等于我的字符串,则返回它的父questEntry元素并搜索嵌套在show中的所有元素。
所以我想要的是匹配包含所需“metaTags”值的文档,并“过滤”任何不包含此内部匹配的子文档数组
这是我尝试使用 $redact
作为聚合查询的方法。 ,但它没有给出我想要的结果:
db.mongoColl.aggregate([{"$redact":{"$cond": { if: {$gt:[ {"$size": {
$setIntersection : [ { "$ifNull": [ "$metaTags", []]},
["MySearchString"]]} } , 0 ]} , then:"$$PRUNE",
else:"$$DESCEND" }}}]).pretty();
我的实例是:
private DB mongoDatabase;
private DBCollection mongoColl;
private DBObject dbObject;
// Singleton class
// Create client (server address(host,port), credential, options)
mongoClient = new MongoClient(new ServerAddress(host, port),
Collections.singletonList(credential),
options);
mongoDatabase = ClientSingleton.getInstance().getClient().getDB("MyDB");
我在数据库中要匹配的文档是:
{
"show":[
{
"season":[
{
"episodes":[
{
"questionEntry":{
"id":1,
"info":{
"seasonNumber":1,
"episodeNumber":5,
"episodeName":"A Hero Sits Next Door"
},
"questionItem":{
"theQuestion":"What is the name of the ringer hired by Mr. Weed?",
"attachedElement":{
"type":1,
"value":""
}
},
"options":[
{
"type":1,
"value":"Johnson"
},
{
"type":1,
"value":"Hideo"
},
{
"type":1,
"value":"Guillermo"
}
],
"answer":{
"questionId":1,
"answer":3
},
"metaTags":[
"Season 1",
"Episode 5",
"Trivia",
"Arya Stark",
"House Stark"
]
}
}
]
}
]
}
]
}
但是,如果文档中的任何数组不包含要匹配的“metaTags”值,即“Arya Stark”,那么我根本不希望该数组的任何元素在结果中匹配。 “metaTags”可以保持原样。
如果这对响应有任何影响,我正在运行最新的 java 驱动程序并在 Eclipse 中使用 java SE1.7 编译器。
最佳答案
$redact
运算符实际上不是这里的最佳选择,或者逻辑很简单,是导致尝试的查询不起作用的主要原因。 “编辑”选项几乎是针对单个特定条件的“全有或全无”过程,并且该条件可用于 $$DESCEND
,因此遍历文档的级别。
通过转置条件中不存在字段的值,最多会得到很多“误报”。最坏的情况是,您最终会删除整个文档,相反它可能是匹配的。它有它的用途,但这并不是真正的用途之一。
首先是一个基于您的结构的简化示例。这主要是为了能够从内容中可视化我们想要过滤的东西:
{
"show": [
{
"name": "Game of Thrones",
"season": [
{
"_id": 1,
"episodes": [
{
"_id": 1,
"metaTags": [
"Arya Stark"
]
},
{
"_id": 2,
"metaTags": [
"John Snow"
]
}
]
},
{
"_id": 2,
"episodes": [
{
"_id": 1,
"metaTags": [
"Arya Stark"
]
}
]
}
]
},
{
"name": "Seinfeld",
"season": [
{
"_id": 1,
"episodes": [
{
"_id": 1,
"metaTags": [
"Jerry Seinfeld"
]
}
]
}
]
}
]
}
这里有两种获得结果的方法。首先有一种传统方法,使用 $unwind
为了使用数组,然后使用 $match
过滤数组和条件表达式,当然有 $group
的几个阶段重建数组的操作:
db.sample.aggregate([
{ "$match": {
"show.season.episodes.metaTags": "Arya Stark"
}},
{ "$unwind": "$show" },
{ "$unwind": "$show.season" },
{ "$unwind": "$show.season.episodes" },
{ "$unwind": "$show.season.episodes.metaTags" },
{ "$group": {
"_id": {
"_id": "$_id",
"show": {
"name": "$show.name",
"season": {
"_id": "$show.season._id",
"episodes": {
"_id": "$show.season.episodes._id",
}
}
}
},
"metaTags": { "$push": "$show.season.episodes.metaTags" },
"matched": {
"$sum": {
"$cond": [
{ "$eq": [ "$show.season.episodes.metaTags", "Arya Stark" ] },
1,
0
]
}
}
}},
{ "$sort": { "_id._id": 1, "_id.show.season.episodes._id": 1 } },
{ "$group": {
"_id": {
"_id": "$_id._id",
"show": {
"name": "$_id.show.name",
"season": {
"_id": "$_id.show.season._id",
},
}
},
"episodes": {
"$push": {
"$cond": [
{ "$gt": [ "$matched", 0 ] },
{
"_id": "$_id.show.season.episodes._id",
"metaTags": "$metaTags"
},
false
]
}
}
}},
{ "$unwind": "$episodes" },
{ "$match": { "episodes": { "$ne": false } } },
{ "$group": {
"_id": "$_id",
"episodes": { "$push": "$episodes" }
}},
{ "$sort": { "_id._id": 1, "_id.show.season._id": 1 } },
{ "$group": {
"_id": {
"_id": "$_id._id",
"show": {
"name": "$_id.show.name"
}
},
"season": {
"$push": {
"_id": "$_id.show.season._id",
"episodes": "$episodes"
}
}
}},
{ "$group": {
"_id": "$_id._id",
"show": {
"$push": {
"name": "$_id.show.name",
"season": "$season"
}
}
}}
])
这一切都很好,而且相当容易理解。然而使用 $unwind
的过程这里会产生大量开销,特别是当我们只讨论文档本身内的过滤,而不是跨文档进行任何分组时。
对此有一种现代方法,但请注意,虽然高效,但它绝对是一个“怪物”,并且在处理嵌入式数组时很容易迷失在逻辑中:
db.sample.aggregate([
{ "$match": {
"show.season.episodes.metaTags": "Arya Stark"
}},
{ "$project": {
"show": {
"$setDifference": [
{ "$map": {
"input": "$show",
"as": "show",
"in": {
"$let": {
"vars": {
"season": {
"$setDifference": [
{ "$map": {
"input": "$$show.season",
"as": "season",
"in": {
"$let": {
"vars": {
"episodes": {
"$setDifference": [
{ "$map": {
"input": "$$season.episodes",
"as": "episode",
"in": {
"$cond": [
{ "$setIsSubset": [
"$$episode.metaTags",
["Arya Stark"]
]},
"$$episode",
false
]
}
}},
[false]
]
}
},
"in": {
"$cond": [
{ "$ne": [ "$$episodes", [] ] },
{
"_id": "$$season._id",
"episodes": "$$episodes"
},
false
]
}
}
}
}},
[false]
]
}
},
"in": {
"$cond": [
{ "$ne": ["$$season", [] ] },
{
"name": "$$show.name",
"season": "$$season"
},
false
]
}
}
}
}},
[false]
]
}
}}
])
其中有大量的数组处理,其中 $map
每个级别以及变量声明 $let
对于每个数组,因为我们都通过 $setDifference
“过滤”内容并测试空数组。
使用单个管道 $project
在初始查询匹配之后,这比之前的过程要快得多。
两者产生相同的过滤结果:
{
"_id" : ObjectId("55b3455e64518e494632fa16"),
"show" : [
{
"name" : "Game of Thrones",
"season" : [
{
"_id" : 1,
"episodes" : [
{
"_id" : 1,
"metaTags" : [
"Arya Stark"
]
}
]
},
{
"_id" : 2,
"episodes" : [
{
"_id" : 1,
"metaTags" : [
"Arya Stark"
]
}
]
}
]
}
]
}
所有“show”、“season”和“episodes”数组都已完全过滤掉与内部“metaTags”条件不匹配的任何文档。 “metaTags”数组本身未受影响,仅通过 $setIsSubset
测试匹配情况。 ,只有这样才能过滤不匹配的“episodes”数组内容。
将其转换为 Java 驱动程序的使用是一个相当简单的过程,因为这只是对象和列表的数据结构表示。同样,您只需使用标准列表和 BSON Document 在 Java 中构建相同的结构即可。对象。但它基本上都是列表和映射语法:
MongoDatabase db = mongoClient.getDatabase("test");
MongoCollection<Document> collection = db.getCollection("sample");
String searchString = new String("Arya Stark");
List<Document> pipeline = Arrays.<Document>asList(
new Document("$match",
new Document("show.season.episodes.metaTags",searchString)
),
new Document("$project",
new Document("show",
new Document("$setDifference",
Arrays.<Object>asList(
new Document("$map",
new Document("input","$show")
.append("as","show")
.append("in",
new Document("$let",
new Document("vars",
new Document("season",
new Document("$setDifference",
Arrays.<Object>asList(
new Document("$map",
new Document("input","$$show.season")
.append("as","season")
.append("in",
new Document("$let",
new Document("vars",
new Document("episodes",
new Document("$setDifference",
Arrays.<Object>asList(
new Document("$map",
new Document("input","$$season.episodes")
.append("as","episode")
.append("in",
new Document("$cond",
Arrays.<Object>asList(
new Document("$setIsSubset",
Arrays.<Object>asList(
"$$episode.metaTags",
Arrays.<Object>asList(searchString)
)
),
"$$episode",
false
)
)
)
),
Arrays.<Object>asList(false)
)
)
)
)
.append("in",
new Document("$cond",
Arrays.<Object>asList(
new Document("$ne",
Arrays.<Object>asList(
"$$episodes",
Arrays.<Object>asList()
)
),
new Document("_id","$$season._id")
.append("episodes","$$episodes"),
false
)
)
)
)
)
),
Arrays.<Object>asList(false)
)
)
)
)
.append("in",
new Document("$cond",
Arrays.<Object>asList(
new Document("$ne",
Arrays.<Object>asList(
"$$season",
Arrays.<Object>asList()
)
),
new Document("name","$$show.name")
.append("season","$$season"),
false
)
)
)
)
)
),
Arrays.<Object>asList(false)
)
)
)
)
);
System.out.println(JSON.serialize(pipeline));
AggregateIterable<Document> result = collection.aggregate(pipeline);
MongoCursor<Document> cursor = result.iterator();
while (cursor.hasNext()) {
Document doc = cursor.next();
System.out.println(doc.toJson());
}
如前所述,这是一个“怪物”语法,它应该可以让您深入了解处理文档中的多层嵌套数组有多么困难。众所周知,单一数组之外的任何内容都很难处理,而且由于位置运算符的限制,基本上不可能执行原子更新。
所以这会起作用,您实际上只需要添加“metaTags”嵌入在“questionEntry”对象中。因此,请将其中的任何内容替换为“questionEntry.metaTags”。但是,您可能会考虑更改此形式的架构,以便使大量编码和维护变得更容易,并使事物可用于原子更新。
关于java - 如何使用 Java 查询和过滤多个嵌套数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31619838/