MongoDB$redact过滤掉数组中的一些元素



我正在尝试对示例bios集合进行查询http://docs.mongodb.org/manual/reference/bios-example-collection/:

检索所有人和他们在获得图灵奖之前获得的奖项

我提出了这个问题:

db.bios.aggregate([
    {$match: {"awards.award" : "Turing Award"}},
    {$project: {"award1": "$awards", "award2": "$awards", "first_name": "$name.first", "last_name": "$name.last"}},
    {$unwind: "$award1"},
    {$match: {"award1.award" : "Turing Award"}},
    {$unwind: "$award2"},
    {$redact: {
        $cond: {
           if: { $eq: [ { $gt: [ "$award1.year", "$award2.year"] }, true]},
           then: "$$KEEP",
           else: "$$PRUNE"
           }
        }
    }
])

这就是答案:

/* 0 */
{
    "result" : [ 
    {
        "_id" : 1,
        "award1" : {
            "award" : "Turing Award",
            "year" : 1977,
            "by" : "ACM"
        },
        "award2" : {
            "award" : "W.W. McDowell Award",
            "year" : 1967,
            "by" : "IEEE Computer Society"
        },
        "first_name" : "John",
        "last_name" : "Backus"
    }, 
    {
        "_id" : 1,
        "award1" : {
            "award" : "Turing Award",
            "year" : 1977,
            "by" : "ACM"
        },
        "award2" : {
            "award" : "National Medal of Science",
            "year" : 1975,
            "by" : "National Science Foundation"
        },
        "first_name" : "John",
        "last_name" : "Backus"
    }, 
    {
        "_id" : 4,
        "award1" : {
            "award" : "Turing Award",
            "year" : 2001,
            "by" : "ACM"
        },
        "award2" : {
            "award" : "Rosing Prize",
            "year" : 1999,
            "by" : "Norwegian Data Association"
        },
        "first_name" : "Kristen",
        "last_name" : "Nygaard"
    }, 
    {
        "_id" : 5,
        "award1" : {
            "award" : "Turing Award",
            "year" : 2001,
            "by" : "ACM"
        },
        "award2" : {
            "award" : "Rosing Prize",
            "year" : 1999,
            "by" : "Norwegian Data Association"
        },
        "first_name" : "Ole-Johan",
        "last_name" : "Dahl"
    }
],
"ok" : 1
}

我不喜欢这个解决方案的地方是我打开了$award2。相反,我很乐意保留award2作为一个数组,只删除award1之后收到的奖项。因此,例如,约翰·巴克斯的答案应该是:

{
    "_id" : 1,
    "first_name" : "John",
    "last_name" : "Backus",
    "award1" : {
        "award" : "Turing Award",
        "year" : 1977,
        "by" : "ACM"
    },
    "award2" : [ 
        {
            "award" : "W.W. McDowell Award",
            "year" : 1967,
            "by" : "IEEE Computer Society"
        }, 
        {
            "award" : "National Medal of Science",
            "year" : 1975,
            "by" : "National Science Foundation"
        }
    ]
}

有没有可能用$redact而不做$unwind: "$award2"来实现它?

如果你在问题中以文档的原始状态为例,这可能会更有帮助,因为这清楚地显示了"你来自哪里",然后将"你想去哪里"作为目标,以及你想要的输出。

这只是一个提示,但看起来你是从这样一个文档开始的:

{
    "_id" : 1,
    "name": { 
        "first" : "John",
        "last" : "Backus"
    },
    "awards" : [
        {
            "award" : "W.W. McDowell Award",
            "year" : 1967,
            "by" : "IEEE Computer Society"
        }, 
        {
            "award" : "National Medal of Science",
            "year" : 1975,
            "by" : "National Science Foundation"
        },
        { 
            "award" : "Turing Award",
            "year" : 1977,
            "by" : "ACM"
        },
        {
            "award" : "Some other award",
            "year" : 1979,
            "by" : "Someone Else"
        }
    ]
}

因此,这里真正的要点是,虽然您可能已经在这里找到了$redact(这比使用$project来处理逻辑条件,然后使用$match来过滤逻辑匹配要好一点),但这可能不是您想要在这里进行比较的最佳工具。

在继续之前,我只想指出$redact的主要问题。无论你在这里做什么,逻辑(没有展开)本质上都是在$$DESCEND上"直接"比较,以便在任何级别处理值为"年"的数组元素。

这种递归也会使"award1"条件无效,因为它具有相同的字段名。即使重命名该字段也会杀死逻辑,因为缺少该字段的投影值不会大于测试值。

简而言之,$redact被排除在外,因为你不能用它所应用的逻辑说"只从这里获取"。

另一种方法是使用$map$setDifference过滤阵列中的内容,如下所示:

db.bios.aggregate([
    { "$match": { "awards.award": "Turing Award" } },
    { "$project": {
        "first_name": "$name.first",
        "last_name": "$name.last",
        "award1": { "$setDifference": [
            { "$map": {
                "input": "$awards",
                "as": "a",
                "in": { "$cond": [
                    { "$eq": [ "$$a.award", "Turing Award" ] },
                    "$$a",
                    false
                ]}
            }},
            [false]
        ]},
        "award2": { "$setDifference": [
            { "$map": {
                "input": "$awards",
                "as": "a",
                "in": { "$cond": [
                    { "$ne": [ "$$a.award", "Turing Award" ] },
                    "$$a",
                    false
                ]}
            }},
            [false]
        ]}
    }},
    { "$unwind": "$award1" },
    { "$project": {
        "first_name": 1,
        "last_name": 1,
        "award1": 1,
        "award2": { "$setDifference": [
            { "$map": {
                "input": "$award2",
                "as": "a",
                "in": { "$cond": [
                     { "$gt": [ "$award1.year", "$$a.year" ] },
                     "$$a",
                     false
                 ]}
            }},
            [false]            
        ]}
    }}
])

实际上,无论是在迭代阶段使用$unwind,还是在这里使用第二个$project,都没有"漂亮"的方法,因为$map(和$setDifference过滤器)返回的是"仍然是一个数组"。因此,$unwind对于使"数组"成为一个单数(前提是您的条件仅与1个元素匹配)条目是必要的,以便在比较中使用。

试图在单个$project中"压缩"所有逻辑只会在第二个输出中产生"数组",因此仍然需要一些"展开",但至少这样展开(希望)1匹配的成本并没有那么高,并且可以保持输出的干净。


但这里需要注意的另一件事是,你根本没有真正"聚合"任何东西。这只是文档操作,所以您可以考虑直接在客户端代码中进行操作。如这个shell示例所示:

db.bios.find(
    { "awards.award": "Turing Award" },
    { "name": 1, "awards": 1 }
).forEach(function(doc) {
    doc.first_name = doc.name.first;
    doc.last_name = doc.name.last;
    doc.award1 = doc.awards.filter(function(award) {
        return award.award == "Turing Award"
    })[0];
    doc.award2 = doc.awards.filter(function(award) {
        return doc.award1.year > award.year;
    });
    delete doc.name;
    delete doc.awards;
    printjson(doc);
})

无论如何,两种方法都会输出相同的

{
    "_id" : 1,
    "first_name" : "John",
    "last_name" : "Backus",
    "award1" : {
            "award" : "Turing Award",
            "year" : 1977,
            "by" : "ACM"
    },
    "award2" : [
            {
                    "award" : "W.W. McDowell Award",
                    "year" : 1967,
                    "by" : "IEEE Computer Society"
            },
            {
                    "award" : "National Medal of Science",
                    "year" : 1975,
                    "by" : "National Science Foundation"
            }
    ]
}

这里唯一真正的区别是,通过使用.aggregate(),"award2"的内容在从服务器返回时已经被过滤,这可能与客户端处理方法没有太大区别,除非将被删除的项目包括每个文档一个相当大的列表。


对于记录,这里真正需要对现有聚合管道进行的唯一更改是在末尾添加$group,以将数组条目"重新组合"到单个文档中:

db.bios.aggregate([
    { "$match": { "awards.award": "Turing Award" } },
    { "$project": {
        "first_name": "$name.first", 
        "last_name": "$name.last",
        "award1": "$awards",
        "award2": "$awards"
    }},
    { "$unwind": "$award1" },
    { "$match": {"award1.award" : "Turing Award" }},
    { "$unwind": "$award2" },
    { "$redact": {
        "$cond": {
             "if": { "$gt": [ "$award1.year", "$award2.year"] },
             "then": "$$KEEP",
             "else": "$$PRUNE"
        }
    }},
    { "$group": {
        "_id": "$_id",
        "first_name": { "$first": "$first_name" },
        "last_name": { "$first": "$last_name" },
        "award1": { "$first": "$award1" },
        "award2": { "$push": "$award2" }
    }}
])

但话说回来,这里的所有操作都有"阵列复制"one_answers"展开成本"。因此,为了避免这种情况,前两种方法中的任何一种都是你真正想要的。

您可以使用带有嵌套表达式的单个项目阶段来避免多个阶段:

db.bios.aggregate([
    {$match : {"awards.award" : "Turing Award"}},
    {$project : {
        award1 : { $arrayElemAt : [{
                    $filter : {
                        input : "$awards",
                        as : "award",
                        cond : {$eq : ["$$award.award","Turing Award"]}
                    }}, 0]},
        award2 : { $let : {
                    vars : {
                    turing_year : { $let : {
                                    vars : {
                                    turingAward :{"$arrayElemAt" : [{"$filter" : {
                                        input : "$awards",
                                        as : "award",
                                        cond : {$eq : ["$$award.award","Turing Award"]}
                                    }}, 0]}},
                                    in : "$$turingAward.year"}}},
                    in : {
                        $filter : {
                            input : "$awards",
                            as : "award",
                            cond : {$lt : ["$$award.year", "$$turing_year"]}
                        }
                    }
            }},
        first_name : "$name.first",
        last_name : "$name.last"}
    }]).pretty();

请查看此处的文档以获取一组有用的数组运算符。

然而,聚合对于这个查询来说并不好看,而且逻辑足够简单,可以在代码本身中实现,不会对性能产生太大影响;只是同意Blakes Seven的回答。但是,MongoDB的一个巧妙之处是,我们可以设计模式来支持我们的访问模式,并保持代码的整洁。如果在实际场景中需要这样的功能,我们可以简单地在文档中包含一个名为"turing_award_year"的字段。这将影响集合上的CRUD操作,但代码将是干净的,现在我们可以使用这样一个非常容易维护的查询:

db.bios.aggregate(
    [
        {$match : {"awards.award" : "Turing Award"}},
        {$project : {
            award1 : { $arrayElemAt : [{
                        $filter : {
                            input : "$awards",
                            as : "award",
                            cond : {$eq : ["$$award.award","Turing Award"]}
                        }}, 0]
            },
            award2 : { $filter : {
                            input : "$awards",
                            as : "award",
                            cond : {$lt : ["$$award.year", "$turing_award_year"]}
                    }
            }
            ,
            first_name : "$name.first",
            last_name : "$name.last"
        }}
    ]
).pretty();

相关内容

  • 没有找到相关文章

最新更新