CouchDB中文档层次结构建模的优化方法

CouchDB中文档层次结构建模的优化方法,couchdb,hierarchical-data,Couchdb,Hierarchical Data,我试图在CouchDB中对文档层次结构进行建模,以便在我的系统中使用,这在概念上类似于博客。每个博客帖子至少属于一个类别,每个类别可以有许多帖子。类别是分层的,这意味着如果一篇文章属于层次结构“CatA->CatB”(“CatB在CatA中”),那么它也属于CatA 用户必须能够快速找到类别中的所有帖子(及其所有子帖子) 解决方案1 post类型的每个文档都包含一个“category”数组,表示其在层次结构中的位置(请参阅) 解决方案2 post类型的每个文档都包含表示其在层次结构中的路径的“c

我试图在CouchDB中对文档层次结构进行建模,以便在我的系统中使用,这在概念上类似于博客。每个博客帖子至少属于一个类别,每个类别可以有许多帖子。类别是分层的,这意味着如果一篇文章属于层次结构“CatA->CatB”(“CatB在CatA中”),那么它也属于CatA

用户必须能够快速找到类别中的所有帖子(及其所有子帖子)

解决方案1 post类型的每个文档都包含一个“category”数组,表示其在层次结构中的位置(请参阅)

解决方案2 post类型的每个文档都包含表示其在层次结构中的路径的“category”字符串(请参阅)

解决方案3 post类型的每个文档都包含其父“类别”id,表示其在层次结构中的路径(请参阅)。通过链接的“类别”文档类型构建分层类别结构

{
   "_id": "8e7a440862347a22f4a1b2ca7f000e83",
   "type": "post",
   "author": "dexter",
   "title": "Hello",
   "category_id": "3"
}

{
   "_id": "1",
   "type": "category",
   "name": "OO"
}


{
   "_id": "2",
   "type": "category",
   "name": "Programming",
   "parent": "1"
}


{
   "_id": "3",
   "type": "category",
   "name": "C++",
   "parent": "2"
}
问题

在CouchDB中存储这种关系的最佳方式是什么?在磁盘空间、可扩展性和检索速度方面,最有效的解决方案是什么

这种关系是否可以建模以考虑本地化的类别名称

免责声明

我知道这个问题在这里已经被问过好几次了,但似乎没有一个明确的答案,也没有一个解决方案利弊的答案。对不起,问题太长:)

阅读到目前为止

这个问题没有正确的答案,因此没有明确的答案。这主要取决于您希望优化的用途

您声明属于特定类别的文档(及其子文档)的检索速度是最重要的。前两个解决方案允许您创建一个视图,该视图可以多次发布博客文章,从叶到根的链中的每个类别都有一次。因此,选择所有文档都可以使用单个(因此是快速)查询完成。第二种解决方案与第一种解决方案的唯一区别在于,将类别“path”的解析从插入文档的代码移动到视图的map函数中的组件中。我更喜欢第一种解决方案,因为它更容易实现map函数,而且更灵活(例如,它允许类别的名称包含斜杠字符)

在您的场景中,您可能还希望创建一个简化视图,该视图统计每个类别的博客文章数量。这两种解决方案都非常简单。通过一个合适的缩减功能,每个类别中的帖子数量都可以通过一个请求来检索

前两种解决方案的缺点是,重命名类别或将类别从一个父级移动到另一个父级需要更新每个文档。第三种解决方案允许在不接触文档的情况下执行此操作。但是根据您的场景描述,我假设按类别检索非常频繁,并且类别重命名/移动非常罕见

解决方案4我提出了第四种解决方案,其中博客文章文档包含对类别文档的引用,但仍然引用文章类别的所有祖先。这允许在不接触博客帖子的情况下重命名类别,并允许您存储带有类别的其他元数据(例如类别名称或描述的翻译):

您仍然需要将类别的父级与类别一起存储,这将复制帖子中的数据,以允许遍历类别(例如,显示用于导航的类别树)

您可以扩展此解决方案或您的任何解决方案,以允许在多个类别下对帖子进行分类,或允许一个类别具有多个父级。当一篇文章被分类为多个类别时,您需要将每个类别的祖先的联合存储在文章的文档中,同时保留作者选择的类别,以允许它们与文章一起显示或稍后编辑

让我们假设还有一个名为“Ajax”的附加类别,其中包含“JavaScript”、“Programming”和“OO”。为了简化下面的示例,我选择了类别的文档ID来等于类别的名称

要允许一个类别具有多个父级,只需将多个父级ID与一个类别一起存储即可。在查找类别的所有祖先时,需要消除重复项

查看解决方案4假设您希望获取特定类别的所有博客帖子。我们将使用包含以下示例数据的数据库:

{ "_id": "100", "type": "category", "name": "OO"                              }
{ "_id": "101", "type": "category", "name": "Programming", "parent_id": "100" }
{ "_id": "102", "type": "category", "name": "C++",         "parent_id": "101" }
{ "_id": "103", "type": "category", "name": "JavaScript",  "parent_id": "101" }
{ "_id": "104", "type": "category", "name": "AJAX",        "parent_id": "103" }

{ "_id": "200", "type": "post", "title": "OO Post",          "category_id": "104", "category_anchestor_ids": ["100"]                      }
{ "_id": "201", "type": "post", "title": "Programming Post", "category_id": "101", "category_anchestor_ids": ["101", "100"]               }
{ "_id": "202", "type": "post", "title": "C++ Post",         "category_id": "102", "category_anchestor_ids": ["102", "101", "100"]        }
{ "_id": "203", "type": "post", "title": "AJAX Post",        "category_id": "104", "category_anchestor_ids": ["104", "103", "101", "100"] }
除此之外,我们在名为
\u design/blog
的设计文档中使用了一个名为
按类别发布的视图,该视图具有以下映射功能:

function (doc) {
    if (doc.type == 'post') {
        for (i in doc.category_anchestor_ids) {
            emit([doc.category_anchestor_ids[i]], doc)
        }
    }
}
然后,我们可以使用
get
请求获取
Programming
类别(其ID为
“101”
)中的所有帖子或其子类别中的一个帖子,请求访问以下URL

http://localhost:5984/so/_design/blog/_view/posts_by_category?reduce=false&key=["101"]
这将返回一个视图结果,其中键设置为category ID,值设置为post文档。同样的视图也可以用于获取所有类别的摘要列表,以及该类别及其子类别中的帖子数量。我们在视图中添加了以下reduce函数:

function (keys, values, rereduce) {
    if (rereduce) {
        return sum(values)
    } else {
        return values.length
    }
}
然后我们使用以下URL:

http://localhost:5984/so/_design/blog/_view/posts_by_category?group_level=1

这将返回一个缩减视图结果,其中键再次设置为类别ID,值设置为每个类别中的帖子数。在本例中,类别名称必须单独提取,但可以创建视图,其中简化视图结果中的每一行都已包含类别名称。

谢谢您的回答!是否有任何理由认为“8e7a440862347a22f4a1b2ca7f000e83”是一个更好的类别选择
function (doc) {
    if (doc.type == 'post') {
        for (i in doc.category_anchestor_ids) {
            emit([doc.category_anchestor_ids[i]], doc)
        }
    }
}
http://localhost:5984/so/_design/blog/_view/posts_by_category?reduce=false&key=["101"]
function (keys, values, rereduce) {
    if (rereduce) {
        return sum(values)
    } else {
        return values.length
    }
}
http://localhost:5984/so/_design/blog/_view/posts_by_category?group_level=1