MongoDB如何获取子文档的属性等于某个值的子文档的不同列表?

时间:2016-02-01 05:58:47

标签: mongodb mongodb-query aggregation-framework

db.test.insert( 
    {
        'name': 'outer',
        'foos': [
            {
                'name': 'a',
                'type': 'bar',
            },
            {
                'name': 'a',
                'type': 'bar',
            },
            {
                'name': 'z',
                'type': 'baz',
            },
            {
                'name': 'z',
                'type': 'baz',
            },
        ]
    }
)

如何在foos等于foo.type的情况下获取bar的独特列表?

我想找到:

[
    {
        'name': 'a',
        'type': 'bar'
    }
]

以下内容不起作用,而是为所有foos返回一个不同的值。

db.test.distinct('foos', {'foos.type': 'bar'})

1 个答案:

答案 0 :(得分:4)

是的,哎呀!这里对这个功能有点误解。这是它返回的内容,我将解释原因:

[
        {
                "name" : "a",
                "type" : "bar"
        },
        {
                "name" : "z",
                "type" : "baz"
        }
]

所以其他条目中“type”等于“baz”,两者现在都是“明显的”,但你错过了你真正的问题。

你确实要求不同的“foos”,这是对的。但是你也只是要求从“文件”中得到它,这个“文档”的数组条目的“type”等于“bar”。这不会将内容“过滤”到那些数组条目,因此您获得其他结果的原因。

因此,您需要在获取“不同”值之前“过滤”内容。你只能用.aggregate()方法做到这一点。 $filter是最好的方法:

db.test.aggregate([
    // Match documents
    { "$match": { "foos.type": "bar" } },

    // Pre-filter the array
    { "$project": {
        "foos": {
            "$filter": {
                "input": "$foos",
                "as": "el",
                "cond": {
                    "$eq": [ "$$el.type", "bar" ]
                }
            }
        }
     }},

     // Unwind the array
     { "$unwind": "$foos" },

     // Group distinct
     { "$group": {
         "_id": "$foos"
     }}
])

或者比MongoDB 3.2早,但版本2.6及更高版本,您可以将$map替换为$setDifference

db.test.aggregate([
    // Match documents
    { "$match": { "foos.type": "bar" } },

    // Pre-filter the array
    { "$project": {
        "foos": {
            "$setDifference": [
                { "$map": {
                    "input": "$foos",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.type", "bar" ] },
                            "$$el",
                            false
                        ]
                    }
                }}
            ]
        }
     }},

     // Unwind the array
     { "$unwind": "$foos" },

     // Group distinct
     { "$group": {
         "_id": "$foos"
     }}
])

同样的事情,$map处理每个数组元素并返回mactched元素或false$setDiffernce删除false个:

最后在2.6以上的任何东西:

db.test.aggregate([
    // Match documents
    { "$match": { "foos.type": "bar" } },

     // Unwind the array
     { "$unwind": "$foos" },

     // Filter the denormalized array
     { "$match": { "foos.type": "bar" } },

     // Group distinct
     { "$group": {
         "_id": "$foos"
     }}
])

一般原则是只留下匹配“type”的数组条目等于“bar”,并且在使用$unwind减少需要的工作之前,理想地“预过滤”数组处理过后,因为过滤后会基本上为每个数组条目创建一个新文档,无论它是否匹配。

无论如何,在某些时候,您需要使用$unwind对数组条目进行“反规范化”,然后使用“foos”(子文档)将$group作为主键值。

这不是.distinct()提供的简单“数组”,而是基本上你只是“清除”你不想考虑的数组条目。

这是要记住的事情,因为正常的查询操作不会“过滤”数组元素,然后类似地,.distinct()的查询输入也不会这样做,其中删除这些元素是你打算做的