如何查询所有子文档

Posted

技术标签:

【中文标题】如何查询所有子文档【英文标题】:How to query all subdocuments 【发布时间】:2012-08-21 18:50:11 【问题描述】:

我从 MongoDb 和 nodejs(使用 mongoose)开始。

我有一组Stories,每个故事都可以有一个或多个Tags,大​​概是这样:


    title: "The red fox",
    content: "The red fox jumps away...",
    tags: [
        
            tagname: "fairytale",
            user: "pippo"
        ,
        
            tagname: "funny",
            user: "pluto"
        ,
        
            tagname: "fox",
            user: "paperino"
        
    ]
,

... other stories

现在我想制作一个标签云

这意味着查询所有标签的故事。

在关系世界(例如 mysql)中,我将有一个 Stories 表、一个 Tags 表和一个 Stories_Tags 表(多对多)。然后我会查询标签表或类似的东西。

有没有办法做到这一点? (我确定是的)

如果是,这是一个好习惯吗?还是打破了 nosql 范式?

你能想出一个更好的方式来设计我的架构吗?

【问题讨论】:

【参考方案1】:

嗯,有不同的方法。而且我认为你的解决方案和this one没有区别。

你也可以复制粘贴它的map_reduce方法来输出tag-count hash。

【讨论】:

我无法理解它(python)。你能在答案中简要解释一下吗?【参考方案2】:

欢迎来到蒙哥

您的数据的最佳“架构”将是这样的。

您创建了一个名为 stories 的集合,每个故事都将是该集合中的一个文档。 然后,您可以使用类似的方式轻松查询您的数据。

db.stories.find( "tags.tagname": "fairytale"); // will find all documents that have fairytale as a tagname.

更新

db.stories.find( "tags.tagname":  $exists : true ); // will find all documents that have a tagname.

注意 find 查询中的点符号,这就是您在 mongo 中访问数组/对象的方式。

【讨论】:

好帖子......但你根本没有明白这一点!我不想按标签获取单个故事。我想获取所有标签(所有标签名)。 好的,我已经更新以查找所有带有标记名的文档,而不是指定实际的标记名。 嗯.... 你让我对我的英语产生了严重的怀疑:) 我的意思只是标签。是否有必要获取所有带有标签的文档?然后什么?如何“选择 DISTINCT 标记名”? 您可以这样做以不从故事文档中检索标题和内容:db.stories.find( "tags.tagname": $exists : true , "title": 0, content: 0 ) 但您需要计算具有这些标签的故事的数量。如果您遵循本教程,您可以执行一个名为 mapped-reduce 的函数:cookbook.mongodb.org/patterns/count_tags @GianPaJ 该教程既好又简单,但是......它结束了创建另一个包含所有标签的集合......我只想让一个查询提取所有标签,然后我将循环、计数和区分他们。有这么奇怪吗?【参考方案3】:

您可以使用 MR 来完成此操作。在 MR 中,您只需挑选标签并投影它们:

var map = function()
     for(var i=0;i<this.tags.length;i++)
         emit(this.tags[i].tagname, count: 1);
     

然后您的 reduce 将遍历发出的文档,基本上总结了该标签被看到的次数。

如果您升级到最新的不稳定 2.2,您还可以使用聚合框架。您将使用聚合框架的 $project 和 $sum piplines 将标签从每个帖子中投影出来,然后将它们相加以创建基于分数的标签云,允许您根据总和调整每个标签的文本大小。

如果是,这是一个好习惯吗?还是打破了 nosql 范式?

这是 MongoDB 中的一个非常标准的问题,而且您不会逃避。随着可重用结构的出现,不可避免地需要对其进行一些复杂的查询。幸运的是,在 2.2 中可以保存aggregationm 框架。

至于这是一个好还是坏的方法,这是一个非常标准的方法,因此它既不好也不坏。

为了使结构更好,您可以将带有计数的唯一标签预先聚合到单独的集合中。这样可以更轻松地实时构建您的标签云。

预聚合是创建通常从 MR 获得的其他集合的一种形式,无需使用 MR 或聚合框架。它通常是基于您的应用程序的事件,因此当用户创建帖子或重新标记帖子时,它将触发预聚合事件到“tag_count”集合,如下所示:


    _id: ,
    tagname: "",
    count: 1

当事件被触发时,您的应用程序将遍历帖子上的标签,基本上像这样执行 $inc upserts:

db.tag_count.update(tagname: 'whoop', $inc: count: 1, true);

因此,您现在将在整个博客中拥有一组标签及其计数。从那里你走与 MR 相同的路线,只需查询此集合即可获取您的数据。您当然需要处理删除和更新事件,但您大致了解。

【讨论】:

所以,如果我现在明白了,mongodb 文档中的“计数标签”教程会创建另一个包含计算结果的集合“标签”吗?每次我想“刷新”标签云时,我都必须重新执行 MR?我不明白你对“预聚合唯一标签”的意思......如果你能帮我解决这个问题,那么正确的答案就是你的:) @FabioB。好的,加了一点解释 @FabioB。尽管您需要确保标签集合中的区别,但这可能会起作用,您可以使用我在答案中显示的 upsert 来确保唯一性 @FabioB。我很惊讶 Stennie 给出了这个答案,distinct 是出了名的慢,我的意思是它确实有效,但至于它是否会在足够快的时间内完成一个大型集合......嗯 `:` 结果并不乐观. 您应该查看 2.2 中发布的聚合框架。看看 Asya Kamsky 的回答。这是正确的做法。【参考方案4】:

这是使用聚合框架执行此操作的方法(您需要使用刚刚发布的 2.2)。

db.stories.aggregate(
[
    
        "$unwind" : "$tags"
    ,
    
        "$group" : 
            "_id" : "$tags.tagname",
            "total" : 
                "$sum" : 1
            
        
    ,
    
        "$sort" : 
            "total" : -1
        
    
])

您的结果将如下所示:


    "result" : [
        
            "_id" : "fairytale",
            "total" : 3
        ,
        
            "_id" : "funny",
            "total" : 2
        ,
        
            "_id" : "silly",
            "total" : 1
        ,
        
            "_id" : "fox",
            "total" : 1
        
    ],
    "ok" : 1

【讨论】:

这比 map reduce 快得多,在我看来更容易理解。

以上是关于如何查询所有子文档的主要内容,如果未能解决你的问题,请参考以下文章

如何对 GORM 子文档字段进行不同的查询?

Mongoose - 查询以获取文档的所有子文档

猫鼬一次查询所有子文档

如何查询和排序 Mongoose 子文档

如何根据查询从子文档数组中更新子文档字段?

如何编写查询来检索每个匹配的子文档数组