12

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

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

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

{
   "_id": "8e7a440862347a22f4a1b2ca7f000e83",
   "type": "post",
   "author": "dexter",
   "title": "Hello",
   "category":["OO","Programming","C++"]
}

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

{
   "_id": "8e7a440862347a22f4a1b2ca7f000e83",
   "type": "post",
   "author": "dexter",
   "title": "Hello",
   "category": "OO/Programming/C++"
}

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

{
   "_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 中存储这种关系的最佳方式是什么?就磁盘空间、可扩展性和检索速度而言,最有效的解决方案是什么?

可以对这种关系进行建模以考虑本地化的类别名称吗?

免责声明

我知道这个问题已经在这里被问过几次了,但似乎没有明确的答案,也没有解决每个解决方案优缺点的答案。抱歉问题的长度:)

读到此为止

CouchDB - 权威指南

在 CouchDB 中存储分层数据

从 CouchDB 检索分层/嵌套数据

将 CouchDB group_level 用于分层数据

4

1 回答 1

24

这个问题没有正确的答案,因此缺乏明确的答案。这主要取决于您要优化的使用类型。

说属于某个类别(及其子类别)的文档的检索速度是最重要的。前两个解决方案允许您创建一个多次发出博客文章的视图,一次针对从叶到根的链中的每个类别。因此,可以使用单个(因此快速)查询来选择所有文档。第二种解决方案与第一种解决方案的唯一区别在于,您将类别“路径”的解析从将文档插入到视图的地图功能的代码中移动到组件中。我更喜欢第一种解决方案,因为它实现地图功能更简单,而且更灵活(例如,它允许类别名称包含斜线字符)。

在您的场景中,您可能还想创建一个精简视图,用于计算每个类别的博客文章数量。使用这些解决方案中的任何一个都非常简单。通过拟合缩减功能,可以使用单个请求检索每个类别中的帖子数量。

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

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

{
    "_id": "8e7a440862347a22f4a1b2ca7f000e83",
    "type": "post",
    "author": "dexter",
    "title": "Hello",
    "category_ids": [3, 2, 1]
}

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

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


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

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

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

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

{
    "_id": "8e7a440862347a22f4a1b2ca7f000e83",
    "type": "post",
    "author": "dexter",
    "title": "Hello",
    "category_ids": ["C++", "Ajax"],
    "category_anchestor_ids": ["C++", "Programming", "OO", "Ajax", "JavaScript"]
}

要允许一个类别有多个父级,只需在一个类别中存储多个父级 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"] }

除此之外,我们使用posts_by_category在设计文档中调用的视图,该视图_design/blog通过以下映射函数调用:

function (doc) {
    if (doc.type == 'post') {
        for (i in doc.category_anchestor_ids) {
            emit([doc.category_anchestor_ids[i]], doc)
        }
    }
}

然后我们可以使用对以下 URL 的请求来获取该Programming类别(具有 ID "101")或其子类别之一中的所有帖子。GET

http://localhost:5984/so/_design/blog/_view/posts_by_category?reduce=false&key=["101"]

这将返回一个视图结果,其中键设置为类别 ID,值设置为发布文档。相同的视图还可用于获取所有类别的摘要列表以及该类别及其子类别中的帖子数量。我们在视图中添加以下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,值设置为每个类别中的帖子数。在此示例中,必须单独获取类别名称,但可以创建简化视图结果中的每一行都已包含类别名称的视图。

于 2013-04-24T11:00:43.347 回答