MongoDb:如何从文档中获取字段(子文档)?

时间:2014-09-18 10:14:46

标签: javascript python mongodb aggregation-framework pymongo

考虑这个示例集合:

 {
    "_id:"0,
    "firstname":"Tom",
    "children" : {
                    "childA":{
                                "toys":{
                                        'toy 1':'batman',
                                        'toy 2':'car',
                                        'toy 3':'train',
                                        }
                                "movies": {
                                        'movie 1': "Ironman"
                                        'movie 2': "Deathwish"
                                        }
                                },
                    "childB":{
                                "toys":{
                                        'toy 1':'doll',
                                        'toy 2':'bike',
                                        'toy 3':'xbox',
                                        }
                                "movies": {
                                        'movie 1': "Frozen"
                                        'movie 2': "Barbie"
                                        }
                                }
                    }
}

现在我只想从特定文档中检索电影。

我尝试过这样的事情:

movies = users.find_one({'_id': 0}, {'_id': 0, 'children.ChildA.movies': 1})

然而,我从“儿童”到“电影”获得整个场地结构,这是内容。我如何只进行查询并仅检索“电影”的内容?

具体来说,我希望最终得到这个:

                                       {
                                        'movie 1': "Frozen"
                                        'movie 2': "Barbie"
                                        }

2 个答案:

答案 0 :(得分:3)

这里的问题是你当前的数据结构对于查询来说并不是很好。这主要是因为你正在使用"键"实际上代表"数据点",虽然它最初似乎是一个合乎逻辑的想法,但它实际上是一种非常糟糕的做法。

所以不要做像分配" childA"和" childB"作为一个对象的键或"子文档",你最好分配这些"值"到这样的结构中的通用键名:

 {
    "_id:"0,
    "firstname":"Tom",
    "children" : [
        { 
            "name": "childA", 
            "toys": [
                "batman",
                "car",
                "train"
            ],
            "movies": [
                "Ironman"
                "Deathwish"
            ]
        },
        {
            "name": "childB",
            "toys": [
                "doll",
                "bike",
                "xbox",
            ],
            "movies": [
                "Frozen",
                "Barbie"
            ]
        }
    ]
}

不是最好的,因为有嵌套数组,这可能是一个潜在的问题,但也有解决方法(但稍后),但这里的要点是这比定义&#34中的数据要好得多;键&#34 ;.以及"键"的主要问题不一致命名的是,MongoDB通常不允许任何方式来通配"通配符"这些名称,所以你坚持命名和"绝对路径"为了访问元素,如:

  

儿童 - > childA - >玩具
     儿童 - > childB - >玩具

简而言之,糟糕,与此相比:

"children.toys"

从上面准备的样本中,我会说这是一个整个更好的组织数据的方法。

即便如此,只需返回诸如"独特的电影列表" 之类的内容,就超出了MongoDB中标准.find()类型查询的范围。这实际上需要更多的文档操作"并且在MongoDB的聚合框架中得到很好的支持。这具有广泛的查询方法中不存在的操作功能,并且作为具有上述结构的每个文档响应,您可以执行此操作:

db.collection.aggregate([
    # De-normalize the array content first
    { "$unwind": "$children" },

    # De-normalize the content from the inner array as well
    { "$unwind": "$children.movies" },

    # Group back, well optionally, but just the "movies" per document
    { "$group": {
        "_id": "$_id",
        "movies": { "$addToSet": "$children.movies" }
    }}
])

所以现在"列表"文档中的响应仅包含" unique"电影,更符合你的要求。或者,你可以只用 $push 来制作一个非常独特的"名单。但愚蠢的是,这实际上与此相同:

db.collection.find({},{ "_id": False, "children.movies": True })

作为"收集广泛"概念,那么你可以通过简单地使用.distinct()方法简化这一过程。这基本上形成了一个" distinct"根据您提供的输入键。这非常适合阵列:

db.collection.distinct("children.toys")

这基本上是对所有" distinct"的收集范围的分析。每个"玩具的出现"集合中的值,并作为简单的#34;数组返回。


但至于你现有的结构,它应该得到一个解释的解决方案,但你真的必须明白解释是可怕的。这里的问题是" native"一般查询和聚合方法可用的优化方法根本不可用,唯一可用的选项是基于JavaScript的处理。即使通过" v8"引擎集成,与本机代码方法并排比较时仍然是一个完整的 slouch

所以来自"原作"你拥有的形式,(JavaScript形式,功能必须如此容易翻译和#34;):

 db.collection.mapReduce(
     // Mapper
     function() {
         var id this._id;
             children = this.children;

         Object.keys(children).forEach(function(child) {
             Object.keys(child).forEach(function(childKey) {
                 Object.keys(childKey).forEach(function(toy) {
                     emit(
                         id, { "toys": [children[childkey]["toys"][toy]] }
                     );
                 });
             });
         });
     },
     // Reducer
     function(key,values) {
         var output = { "toys": [] };

         values.forEach(function(value) {
             value.toys.forEach(function(toy) {
                 if ( ouput.toys.indexOf( toy ) == -1 )
                     output.toys.push( toy );
             });
         });
     },
     {
         "out": { "inline": 1 }
     }
)

因此,JavaScript评估是"可怕的" 方法,因为执行速度要慢得多,并且您会看到"遍历"需要实现的代码。表现不好的消息,所以不要这样做。改为改变结构。


作为最后一部分,您可以采用不同的方式对其进行建模以避免使用#34;嵌套数组"概念。并且了解"嵌套数组的唯一真正的问题"就是"更新"如果不读取整个文档并进行修改,嵌套元素是不可能的。

所以 $push $pull 方法运行正常。但是使用"位置" $运算符只是作为"外部"数组索引始终是"第一个"匹配元素。所以,如果这对你来说确实是一个问题,那么你可以做这样的事情,例如:

 {
    "_id:"0,
    "firstname":"Tom",
    "childtoys" : [
        { 
            "name": "childA", 
            "toy": "batman"
        }.
        { 
            "name": "childA",
            "toy": "car"
        },
        {
            "name": "childA",
            "toy": "train"
        },
        {
            "name": "childB",
            "toy": "doll"
        },
        {
            "name": "childB",
            "toy": "bike"
        },
        {
            "name": "childB",
            "toy": "xbox"
        }
    ],
    "childMovies": [
        {
             "name": "childA"
             "movie": "Ironman"
       },
       {
            "name": "childA",
            "movie": "Deathwish"
       },
       {
            "name": "childB",
            "movie": "Frozen"
       },
       {
            "name": "childB",
            "movie": "Barbie"
       }
  ]
}

如果确实需要"更新"这将是避免嵌套更新问题的一种方法。定期的物品,而不仅仅是$ push和$ pull物品到"玩具"和#34;电影"阵列。

但这里的总体信息是围绕您实际使用的访问模式设计数据。 MongoDB通常不喜欢使用"严格路径"在能够查询或以其他方式灵活地发布更新的方面。

答案 1 :(得分:0)

MongoDB中的预测使用' 1'和' 0' ,不是'真' /'错误'。 此外,请确保在正确的情况下(大写/小写)

指定字段

查询应如下所示:

db.users.findOne({'_id': 0}, {'_id': 0, 'children.childA.movies': 1})

这将导致:

{
    "children" : {
        "childA" : {
            "movies" : {
                "movie 1" : "Ironman",
                "movie 2" : "Deathwish"
            }
        }
    }
}