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之后收到的奖项。因此,例如,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(和$setDifferencefilter)返回的是“仍然是数组”。因此,$unwind有必要使“数组”成为一个单数(如果您的条件仅匹配1个元素)条目,以供比较使用。

试图“压缩”单个逻辑$project只会在第二个输出中导致“数组数组”,因此仍然需要一些“展开”,但是至少以这种方式展开(希望)1个匹配并不是真的成本高昂,并保持输出清洁。


但是这里要真正注意的另一件事是,您根本没有真正在“聚合”任何东西。这只是文档操作,因此您不妨考虑直接在客户端代码中进行该操作。如以下示例所示:

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在末尾添加a ,以将数组条目“重新组合”到单个文档中:

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" }
    }}
])

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章