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

标签 mongodb mongodb-query aggregation-framework

我正在尝试对示例 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 之后收到的那些奖项。因此,例如,John Backus 的答案应该是:

{
    "_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 上“直接”比较,以便在任何级别处理“year”值的数组元素。

该递归也将使“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" }
    }}
])

但话又说回来,这里的所有操作都与所有“数组重复”和“展开成本”相关联。因此,前两种方法中的任何一种都是您真正想要避免的。

关于MongoDB $redact 过滤掉数组的一些元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31045737/

相关文章:

mongodb - "group by"对 meteor 收集的查询

mongodb - 在 Mongo 中使用唯一键创建 sawMessages 对象

node.js - Mongoose 填充查询返回带有跳过/限制的所有结果

mongodb - 字段 "$_id"必须是累加器对象

MongoDB 复合索引优化键和范围条件更新

带有 $lookup 的 MongoDB 聚合仅包括(或投影)一些要从查询返回的字段

c# - 如何从 MongoDB 中的 ChangeStream 过滤对特定字段的更新

mongodb - Morphia 在性能方面 fetch 和 asList 之间有区别吗

javascript - MongoDB - 查询嵌套数组中的嵌套对象

php - mongodb中按年龄组分组