在嵌套文档中对记录进行分组

时间:2014-08-15 06:56:57

标签: mongodb mapreduce aggregation-framework

我有这样的文件:

{
"_id" : ObjectId("533e6ab0ef2188940b00002c"),
"uin" : "1396599472869",
"vm" : {
    "0" : {
        "draw" : "01s",
        "count" : "2",
        "type" : "",
        "data" : {
            "title" : "K1"
        },
        "child" : [ 
            "1407484608965"
        ]
    },
    "1407484608965" : {
        "data" : {
            "title" : "K2",
            "draw" : "1407473540857",
            "count" : "1",
            "type" : "Block"
        },
        "child" : [ 
            "1407484647012"
        ]
    },
    "1407484647012" : {
        "data" : {
            "title" : "K3",
            "draw" : "03.8878.98",
            "count" : "1",
            "type" : "SB"
        },
        "child" : [ 
            "1407484762473"
        ]
    },
    "1407484762473" : {
        "data" : {
            "type" : "SB",
            "title" : "D1",
            "draw" : "7984",
            "count" : "1"
        },
        "child" : []
    }
}
}

如何用条件(type =“Block”)对所有记录进行分组?

我试过了:       db.ITR.aggregate({$匹配:{ “UIN”: “1396599472869”}},{$项目:{ “VM”:1}},{$组:{_ ID:空时,r1:{$推: “$ VM”}}},{$开卷: “$ R1”},{$组:{_ ID:空,R2:{$推: “$ R1”}}},{$开卷:“$ R2 “})

但结果仍然是对象而不是数组。使用“MapReduce”我没有得到。

1 个答案:

答案 0 :(得分:1)

这里的问题基本上与您目前的文档结构方式有关。使用"键"在" vm"这里实际上标识数据点并不能很好地与标准查询表单和聚合框架一起使用。

它通常也不是一个非常好的模式,因为为了访问" vm"你需要指定"确切路径"对数据。所以寻找类型" Block"需要这个:

db.collection.find({
    "$or": [
        { "vm.0.type": "Block" },
        { "vm.1407484608965.type": "Block" }
        { ... }
    ]
})

等等。你不能"通配符"像这样的字段名称,所以确切的路径是必需的。

更好的建模方法是使用数组,并将内部键移动到文档中:

{
    "_id" : ObjectId("533e6ab0ef2188940b00002c"),
    "uin" : "1396599472869",
    "vm" : [
        {
            "key": 0,
            "draw" : "01s",
            "count" : "2",
            "type" : "",
            "data" : {
                "title" : "K1"
            },
            "child" : [ 
                "1407484608965"
            ]
        },
        {
            "key": "1407484608965",
            "title" : "K2",
            "draw" : "1407473540857",
            "count" : "1",
            "type" : "Block",
            "child" : [ 
                "1407484647012"
            ]
        },
        {
            "key": "1407484647012",
            "title" : "K3",
            "draw" : "03.8878.98",
            "count" : "1",
            "type" : "SB",
            "child" : [ 
                "1407484762473"
            ]
        }
    ]
}

这允许您通过公共路径查询包含匹配属性的文档,这极大地简化了事情:

db.collection.find({ "vm.type": "Block" })

或者如果你想"过滤"数组内容只有那些"子文档"返回该匹配,你可以这样做:

db.collection.aggregate([
    { "$match": { "vm.type": "Block" } },
    { "$unwind": "$vm" },
    { "$match": { "vm.type": "Block" } },
    { "$group": {
        "_id": "$_id",
        "uin": { "$first": "$uin" },
        "vm": { "$push": "$vm" }
    }}
])

甚至可能使用MongoDB 2.6或更高版本:

db.collection.aggregate([
    { "$match": { "vm.type": "Block" } },
    { "$project": {
        "uin": 1,
        "vm": {
            "$setDifference": [
                { "$map": {
                    "input": "$vm",
                    "as": "el",
                    "in": {"$cond": [
                        { "$eq": [ "$$el.type", "Block" ] },
                        "$$el",
                        false
                    ]}
                }},
                [false]
            ]
        }
    }}
])

或者任何其他操作,现在简化为遍历,数据以这种方式构建。但是,由于您的数据目前是您唯一选择"遍历键"是使用JavaScript操作,这比以正确方式查询要慢得多:

db.collection.find(function() {
    return Object.keys(this.vm).some(function(x) { 
        return this.vm[x].type == "Block" 
    })
})

或者使用mapReduce进行类似的对象处理,但基本上没有其他方法来访问具有固定路径的字段,这些路径一直在变化。

也许这是为了避免使用嵌套数组而进入的设计。这是"孩子"元素将被放置。当然这会带来更新问题。但实际上,如果任何元素不应该是一个数组,它可能是"内部"元素如" child",它可能有某种不使用数组的结构。

因此关键是要考虑重组,因为这可能适合您想要的模式,而不会导致JavaScript遍历将引入的性能问题。