MongoDB聚合:如何仅返回数组中匹配的元素

13
在我的 MongoDB 图书收藏中,我的文档结构如下:
/* 0 */
{
  "_id" : ObjectId("50485b89b30f1ea69110ff4c"),

  "publisher" : {
    "$ref" : "boohya",
    "$id" : "foo"
  },
  "displayName" : "Paris Nightlife",
  "catalogDescription" : "Some desc goes here",
  "languageCode" : "en",
  "rating" : 0,
  "status" : "LIVE",
  "thumbnailId" : ObjectId("50485b89b30f1ea69110ff4b"),
  "indexTokens" : ["Nightlife", "Paris"]
}

我执行以下正则表达式查询,以查找所有具有以“Par”开头的一个索引标记的文档:

{ "indexTokens" : { "$regex" : "^Par" , "$options" : "i"}}
如果我像这样仅选择返回indexTokens字段:
{ "indexTokens" : 1}
The resulting DBObject is
{ "_id" : { "$oid" : "50485b89b30f1ea69110ff4c"} , "indexTokens" : [ "Nightlife" , "Paris"]}

我想要的仅是与正则表达式匹配的标记/标签(此时我不关心检索文档,也不需要匹配文档的所有标记)

这是否适用于MongoDB v2.2发布的新聚合框架?

如果是,我应该如何修改查询,以便实际结果看起来像:

{"indexTokens":["Paris","Paradise River","Parma"等]}

奖励问题(你有代码吗):如何使用Java驱动程序进行操作?

目前我的Java代码类似于:

DBObject query = new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+filter+"", Pattern.CASE_INSENSITIVE));
    BasicDBObject fields = new BasicDBObject("indexTokens",1);
    DBCursor curs = getCollection()
                    .find(query, fields)
                    .sort( new BasicDBObject( "indexTokens" , 1 ))
                    .limit(maxSuggestionCount);

谢谢:)

编辑:

根据您的答复,我已修改了我的JAVA代码如下:

BasicDBObject cmdBody = new BasicDBObject("aggregate", "Book"); 
    ArrayList<BasicDBObject> pipeline = new ArrayList<BasicDBObject>(); 

    BasicDBObject match = new BasicDBObject("$match", new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+titleFilter+"", Pattern.CASE_INSENSITIVE)));
    BasicDBObject unwind = new BasicDBObject("$unwind", "$indexTokens");
    BasicDBObject match2 = new BasicDBObject("$match", new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+titleFilter+"", Pattern.CASE_INSENSITIVE)));
    BasicDBObject groupFilters = new BasicDBObject("_id",null);
    groupFilters.append("indexTokens", new BasicDBObject( "$push", "$indexTokens"));
    BasicDBObject group = new BasicDBObject("$group", groupFilters);

    pipeline.add(match);
    pipeline.add(unwind);
    pipeline.add(match2);
    pipeline.add(group);

    cmdBody.put("pipeline", pipeline); 



    CommandResult res = getCollection().getDB().command(cmdBody);
    System.out.println(res);

输出结果为

{ "result" : [ { "_id" :  null  , "indexTokens" : [ "Paris"]}] , "ok" : 1.0}

这太聪明了!

非常感谢!

2个回答

13
你可以使用2.2聚合框架来完成此操作。大致如下:
db.books.runCommand("aggregate", {
    pipeline: [
        {   // find docs that contain Par*
            $match: { "indexTokens" : { "$regex" : "^Par" , "$options" : "i"}},
        },
        {   // create a doc with a single array elemm for each indexToken entry
            $unwind: "$indexTokens" 
        },
        {   // now produce a list of index tokens
            $group: {
                _id: "$indexTokens",
            },
        },
    ],
})

如果你真的想要没有文档的数组,那么以下内容可能更符合你的需求:

db.books.runCommand("aggregate", {
    pipeline: [
        {   // find docs that contain Par*
            $match: { "indexTokens" : { "$regex" : "^Par" , "$options" : "i"}},
        },
        {   // create a doc with a single array elemm for each indexToken entry
            $unwind: "$indexTokens" 
        },
        {   // now throw out any unwind's that DON'T contain Par*
            $match: { "indexTokens": { "$regex": "^Par", "$options": "i" } },
        },
        {   // now produce the list of index tokens
            $group: {
                _id: null,
                indexTokens: { $push: "$indexTokens" },
            },
        },
    ],
})

您可以将此作为第二个解决方案添加到您的原始答案中。这样人们就不会困惑为什么有两个来自您的答案 :) - Sammaye
多谢你们两位,现在它完美地运行了。我添加了一个答案来展示我如何在JAVA中实现它(我没有最新的驱动程序,所以无法在DBCollection上使用aggregate()方法)。 - azpublic

5

在cirrus的回复基础上,我建议先执行$unwind以避免冗余的$match。类似这样:

db.books.aggregate(
    {$unwind:"$indexTokens"},
    {$match:{indexTokens:/^Par/}},
    {$group:{_id:null,indexTokens:{$push:"$indexTokens"}}
})

你如何在Java中实现这一点?您可以使用MongoDB v2.9.0驱动程序的DBCollection.aggregate(...)方法。每个管道操作符,例如$unwind$match,对应于一个DBObject对象。

1
实际上,我认为$match并不是多余的。$unwind的问题在于它将不得不在RAM中创建一个大量的文档集,并且您希望尽早将该集合缩小。第一个$match确保我们仅使用具有Par*索引标记的文档才进行展开。第二个$match然后将该集合减少到我们想要的内容。请记住,您需要尽早使用$match来减少管道容量。 - cirrus
1
你是对的。清除不匹配的文档,展开数组,然后再次匹配以清除不符合正则表达式的文档。 - slee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接