【问题标题】:MongoDb: How to get a field (sub document) from a document?MongoDb:如何从文档中获取字段(子文档)?
【发布时间】:2014-11-12 15:10:36
【问题描述】:

考虑这个示例集合:

 {
    "_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"
                                        }

【问题讨论】:

  • 您在给定数据中的 id 字段看起来很乱?
  • Id 字段很好。这只是一个通用示例。

标签: javascript python mongodb aggregation-framework pymongo


【解决方案1】:

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

因此,与其将“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"
            ]
        }
    ]
}

不是最好的,因为有嵌套数组,这可能是一个潜在的问题,但也有解决方法(但稍后),但这里的要点是这比在“键”中定义数据要好得多.名称不一致的“键”的主要问题是 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" }
    }}
])

因此,现在文档中的“列表”响应仅包含“独特”电影,这更符合您的要求。或者,您可以改为 $push 并制作一个“非唯一”列表。但愚蠢的是,这实际上是一样的:

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

作为“集合范围”的概念,您可以通过简单地使用.distinct() 方法来简化这一点。它基本上根据您提供的输入形成“不同”键的列表。这非常适合数组:

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

这本质上是对集合中每个“toys”值的所有“不同”事件的集合范围分析,并作为简单的“数组”返回。


但是对于你现有的结构,它值得一个解决方案来解释,但你真的必须明白这个解释是可怕的。这里的问题是通用查询和聚合方法可用的“本机”和优化方法根本不可用,唯一可用的选项是基于 JavaScript 的处理。尽管通过“v8”引擎集成稍微好一点,但与原生代码方法相比,它仍然是一个完整的懒散

所以从你所拥有的“原始”形式来看,(JavaScript 形式,函数必须很容易翻译”):

 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 评估是 “可怕” 的方法,因为它的执行速度要慢得多,并且您会看到需要实现的“遍历”代码。性能的坏消息,所以不要这样做。改为更改结构。


作为最后一部分,您可以对它进行不同的建模以避免“嵌套数组”概念。并且了解“嵌套数组”唯一真正的问题是,如果不读取整个文档并对其进行修改,“更新”嵌套元素确实是不可能的。

所以 $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 项目添加到“toys”和“movies”数组,这将是避免嵌套更新问题的一种方法。

但这里的总体信息是围绕您实际使用的访问模式设计数据。就能够查询或以其他方式灵活发布更新而言,MongoDB 通常不喜欢具有“严格路径”的事物。

【讨论】:

  • 感谢您的详尽回答。当我设置我的下一个 mongo 数据库时,它肯定给了我很多思考。所以,基本上对我的问题的简短回答是没有简单的方法来做我想做的事情,因为我的数据库结构现在。再次感谢! :)
【解决方案2】:

MongoDB 中的投影使用 '1' 和 '0' ,而不是 'True'/'False'。 此外确保以正确的大小写(大写/小写)指定字段

查询应该如下:

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

这将导致:

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

【讨论】:

  • 我更改了我的问题以在投影中反映 0 和 1,而不是 False 和 True。你提供的答案就是我现在得到的。如果可能的话,我只想要“电影”的内容。我还编辑了我的问题以提供我正在寻找的特定输出。我什至不确定它是否可以完成。
猜你喜欢
  • 2022-11-24
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-02
  • 2022-08-24
  • 2023-03-28
  • 2018-05-17
  • 1970-01-01
相关资源
最近更新 更多