排除由位置运算符检索的匹配元素中的字段

时间:2014-06-06 19:40:48

标签: mongodb mongodb-query aggregation-framework

我有一个texts集合,其文档如下所示:

{
    title: 'A title',
    author: 'Author Name',
    published: 1944,
    languages: [
        {
            code: 'en',
            text: 'A long english text by the author...'
        },
        {
            code: 'da',
            text: 'En lang dansk tekst skrevet af forfatteren...'
        }
        // + many more languages
    ]    
}

并且想要查询检索标题,作者和发布日期以及给定语言的文本,所以我这样做:

texts.findOne(
    { title: titleArg, language.code: languageArg },
    { 'title': 1, 'author': 1, 'published': 1, 'languages.$': 1 } ...

但是我希望返回匹配的语言元素WITHOUT mongodb的_id字段。

如果我在投影中这样做:

{ '_id': 0, 'title': 1, 'author': 1, 'published': 1, 'languages.$': 1 }

我没有它的主要_id就把文档拿回来了,但如果我这样做了:

{ 'languages.$._id': 0, 'title': 1, 'author': 1, 'published': 1, 'languages.$': 1 }

或者这个:

{ 'languages._id': 0, 'title': 1, 'author': 1, 'published': 1, 'languages.$': 1 }

什么都没有归还。

有没有人知道如何创建一个返回数组元素的投影并排除该元素中的某些字段?

1 个答案:

答案 0 :(得分:1)

你似乎真的在说你的文件真的像这样:

{
    "_id": ObjectId("53b25ad420edfc7d0df16a0c"),
    "title": "A title",
    "author": "Author Name",
    "published": 1944,
    "languages": [
        {
            "_id": ObjectId("53b25af720edfc7d0df16a0d"),
            "code": "en",
            "text": "A long english text by the author..."
        },
        {
            "_id": ObjectId("53b25b0720edfc7d0df16a0e"),
            "code": "da",
            "text": "En lang dansk tekst skrevet af forfatteren..."
        }
    ]    
}

为了澄清这一点,MongoDB不会在数组元素中插入_id值。这是某些Object Document Mappers或ODM所做的事情,其中​​一个ODM是猫鼬。但是正如其他软件一样,MongoDB和默认驱动程序只将此字段放在集合中文档的“顶层”,除非在其中指定了另一个值。

在您希望“投射”的数组中的字段中具体或精确,超出了您使用.find()所能做的范围。您实际上需要.aggregate()方法才能“重新塑造”文档并按照您希望的方式删除所有_id字段:

db.collection.aggregate([
    // Match the document(s) that meet the conditions
    { "$match": { 
        "title": "A title",
        "languages.code": "en"
    }},

    // Unwind the array to de-normalize for processing
    { "$unwind": "$languages" },

    // Match to "filter" the actual array documents
    { "$match": { "languages.code": "en" } },

    // Group back the array per document and keep only the wanted fields
    { "$group": {
        "_id": "$_id",
        "title": { "$first": "$title" },
        "author": { "$first": "$author" },
        "published": { "$first": "$published" },
        "languages": {
            "$push": {
                "code": "$languages.code",
                "text": "$languages.text"
            }
        }
    }},

    // Finally project to remove the "root" _id field
    { "$project": {
        "_id": 0,
        "title": 1,
        "author": 1,
        "published": 1,
        "languages": 1
    }}
])

MongoDB 2.6引入了一些新的运算符,可以在一个$project阶段实现此过程:

db.collection.aggregate([
    { "$match": { 
        "title": "A title",
        "languages.code": "en"
    }},
    { "$project": {
        "_id": 0,
        "title": 1,
        "author": 1,
        "published": 1,
        "languages": {
            "$setDifference": [
                { "$map": {
                    "input": "$languages",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.code", "en" ] },
                            { "code": "$$el.code", "text": "$$el.text" },
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])

此类操作的一般意图通常是用于比您正在进行的更复杂的文档重新整形,包括需要匹配多个数组元素,这是您无法通过位置投影进行的。

但这也是唯一能够“改变”数组元素中返回的字段的方法。

另请查看aggregation operators的完整列表,以获取每个{{3}}的详细说明。