如何使用 Java 查询和过滤多个嵌套数组
How to Query and Filter Multiple Nested Arrays with Java
我的目标是 return 多个 questionElements,其中 questionElements 元标记条目等于我的搜索。例如。如果 metaTag 元素等于我的字符串 return 它是父 questionEntry 元素并搜索嵌套在 show.
中的所有元素
所以我想要的是匹配包含所需 "metaTags" 值的文档,以及 "filter" 不包含此内部匹配项的任何子文档数组
这是我尝试使用 $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 编译器(如果这对响应有任何影响的话)。
您可以使用以下代码进行聚合:
mongoClient = new MongoClient("127.0.0.1", 27017);
DB db = mongoClient.getDB("db_name");
DBCollection dbCollection = db.getCollection("collection_name");
//make aggregation pipeline here
List<DBObject> pipeline = new ArrayList<DBObject>();
AggregationOutput output = dbCollection.aggregate(pipeline);
List<DBObject> results = (List<DBObject>) output.results();
//iterate this list and cast DBObject to your POJO
您可以将 DBObject
转换为 POJO
或者可以使用以下方法从 DBObject
获取值:
dbObject.get("key");
$redact
运算符确实不是这里的最佳选择,或者逻辑是否如此简单,是导致尝试的查询不起作用的主要原因。 "redaction" 选项几乎是针对单个特定条件的 "all or nothing" 过程,该条件可用于 $$DESCEND
,因此遍历文档的级别。
充其量你可以通过在编码中的字段不存在的地方转置一个值来获得很多 "false positives"。在最坏的情况下,您最终会删除整个文档,相反它可能是一个匹配项。它有它的用途,但这不是其中之一。
首先是一个基于您的结构的简化样本。这主要是为了能够从内容中可视化我们要过滤的东西:
{
"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
in order to work with the arrays, which are then filtered using $match
and conditional expressions with of course serveral stages of $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
的过程会产生大量开销,尤其是当我们只讨论在文档本身内进行过滤,而不是跨文档进行任何分组时。
有一种现代方法可以解决这个问题,但请注意,虽然高效,但它是绝对的 "monster" 并且在处理嵌入式数组时很容易迷失在逻辑中:
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
and each level as well as variable declarations with $let
for each array, since we are both "filtering" content via $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 驱动程序是一个相当简单的过程,因为这只是对象和列表的数据结构表示。在同一个 wat 中,您只需使用标准列表和 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());
}
如前所述,这是一个 "monster" 语法,它应该让您深入了解处理文档中的多层嵌套数组有多么困难。众所周知,超出单个数组的任何事物都难以处理,并且由于位置运算符的限制,基本上不可能对其执行原子更新。
所以这会起作用,你真的只需要添加 "metaTags" 嵌入在 "questionEntry" 对象中。所以用 "questionEntry.metaTags" 代替那里的任何东西。但是您可能会考虑从这种形式更改您的模式,以便在大量编码和维护中简化工作,并使内容可用于原子更新。
我的目标是 return 多个 questionElements,其中 questionElements 元标记条目等于我的搜索。例如。如果 metaTag 元素等于我的字符串 return 它是父 questionEntry 元素并搜索嵌套在 show.
中的所有元素所以我想要的是匹配包含所需 "metaTags" 值的文档,以及 "filter" 不包含此内部匹配项的任何子文档数组
这是我尝试使用 $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 编译器(如果这对响应有任何影响的话)。
您可以使用以下代码进行聚合:
mongoClient = new MongoClient("127.0.0.1", 27017);
DB db = mongoClient.getDB("db_name");
DBCollection dbCollection = db.getCollection("collection_name");
//make aggregation pipeline here
List<DBObject> pipeline = new ArrayList<DBObject>();
AggregationOutput output = dbCollection.aggregate(pipeline);
List<DBObject> results = (List<DBObject>) output.results();
//iterate this list and cast DBObject to your POJO
您可以将 DBObject
转换为 POJO
或者可以使用以下方法从 DBObject
获取值:
dbObject.get("key");
$redact
运算符确实不是这里的最佳选择,或者逻辑是否如此简单,是导致尝试的查询不起作用的主要原因。 "redaction" 选项几乎是针对单个特定条件的 "all or nothing" 过程,该条件可用于 $$DESCEND
,因此遍历文档的级别。
充其量你可以通过在编码中的字段不存在的地方转置一个值来获得很多 "false positives"。在最坏的情况下,您最终会删除整个文档,相反它可能是一个匹配项。它有它的用途,但这不是其中之一。
首先是一个基于您的结构的简化样本。这主要是为了能够从内容中可视化我们要过滤的东西:
{
"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
in order to work with the arrays, which are then filtered using $match
and conditional expressions with of course serveral stages of $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
的过程会产生大量开销,尤其是当我们只讨论在文档本身内进行过滤,而不是跨文档进行任何分组时。
有一种现代方法可以解决这个问题,但请注意,虽然高效,但它是绝对的 "monster" 并且在处理嵌入式数组时很容易迷失在逻辑中:
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
and each level as well as variable declarations with $let
for each array, since we are both "filtering" content via $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 驱动程序是一个相当简单的过程,因为这只是对象和列表的数据结构表示。在同一个 wat 中,您只需使用标准列表和 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());
}
如前所述,这是一个 "monster" 语法,它应该让您深入了解处理文档中的多层嵌套数组有多么困难。众所周知,超出单个数组的任何事物都难以处理,并且由于位置运算符的限制,基本上不可能对其执行原子更新。
所以这会起作用,你真的只需要添加 "metaTags" 嵌入在 "questionEntry" 对象中。所以用 "questionEntry.metaTags" 代替那里的任何东西。但是您可能会考虑从这种形式更改您的模式,以便在大量编码和维护中简化工作,并使内容可用于原子更新。