java - 如何使用 Java 查询和过滤多个嵌套数组

标签 java mongodb mongodb-query aggregation-framework

我的目标是返回多个 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/

相关文章:

java - 局部变量未初始化,但仍在编译程序

java - 当 Windows 连接到网络时收到通知

mongodb - 如何使用 mongotemplate 更新数组列表?

mongodb - 如何获得总和的总和拆分,这也是mongoDB中项目的总和

java - 在 junit 测试中获取一个类作为 javax.lang.model.element.Element

java - 使用 GET 将数据对象从客户端传输到服务器以持久保存数据的方式是什么?

java - 使用 MapReduce 从多个 MongoDB 集合中搜索 - Java

mongodb - 按 Mongodb 中字段的平均值计算文档

mongodb - 如何在 MongoDB 更新中将字段的值推送到同一文档的另一个数组字段中

c# - 在 MongoDB 的嵌套文档中插入新文档