在CouchDB中建模文档层次结构的最佳方法

Dex*_*ter 12 couchdb hierarchical-data

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

用户必须能够快速查找某个类别(及其所有子级)中的所有帖子.

解决方案1 post类型的每个文档都包含一个"category"数组,表示它在层次结构中的位置(参见2).

{
   "_id": "8e7a440862347a22f4a1b2ca7f000e83",
   "type": "post",
   "author": "dexter",
   "title": "Hello",
   "category":["OO","Programming","C++"]
}
Run Code Online (Sandbox Code Playgroud)

解决方案2 post类型的每个文档都包含表示层次结构中路径的"category"字符串(参见4).

{
   "_id": "8e7a440862347a22f4a1b2ca7f000e83",
   "type": "post",
   "author": "dexter",
   "title": "Hello",
   "category": "OO/Programming/C++"
}
Run Code Online (Sandbox Code Playgroud)

解决方案3 post类型的每个文档都包含其父类"category"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"
}
Run Code Online (Sandbox Code Playgroud)

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

这样的关系可以建模以考虑本地化的类别名称吗?

放弃

我知道这个问题已经在SO上问了好几次,但似乎没有明确的答案,也没有一个答案可以解决每个解决方案的优缺点.对不起问题的长度:)

到目前为止阅读

CouchDB - 权威指南

在CouchDB中存储分层数据

从CouchDB中检索分层/嵌套数据

使用CouchDB group_level进行分层数据

Feu*_*mel 24

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

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

在您的方案中,您可能还希望创建一个简化视图,该视图计算每个类别的博客帖子数.使用这些解决方案中的任何一个都非常简单.使用拟合缩减功能,可以使用单个请求检索每个类别中的帖子数.

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

解决方案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"
}
Run Code Online (Sandbox Code Playgroud)

您仍然必须使用类别存储类别的父级,这些类别是帖子中的重复数据,以允许遍历类别(例如,用于显示导航类别树).

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

让我们假设有一个名为"Ajax"的附加类别,其中包含anchestors"JavaScript","Programming"和"OO".为了简化以下示例,我选择了类别的文档ID以等于类别的名称.

{
    "_id": "8e7a440862347a22f4a1b2ca7f000e83",
    "type": "post",
    "author": "dexter",
    "title": "Hello",
    "category_ids": ["C++", "Ajax"],
    "category_anchestor_ids": ["C++", "Programming", "OO", "Ajax", "JavaScript"]
}
Run Code Online (Sandbox Code Playgroud)

要允许类别具有多个父项,只需存储具有类别的多个父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"] }
Run Code Online (Sandbox Code Playgroud)

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

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

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

http://localhost:5984/so/_design/blog/_view/posts_by_category?reduce=false&key=["101"]
Run Code Online (Sandbox Code Playgroud)

这将返回一个视图结果,其中键设置为类别ID,并且值设置为发布文档.同样的视图也可用于获取所有类别的摘要列表以及该类别中的帖子数量及其子项.我们在视图中添加以下reduce函数:

function (keys, values, rereduce) {
    if (rereduce) {
        return sum(values)
    } else {
        return values.length
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们使用以下URL:

http://localhost:5984/so/_design/blog/_view/posts_by_category?group_level=1
Run Code Online (Sandbox Code Playgroud)

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

  • @Dexter你走了.现在你必须赞成这个答案_really hard_;). (3认同)
  • _8e7a4408 ... _实际上是博客文章的ID.但是,有很多理由不使用类别的名称作为ID:允许类别名称中的保留字符,重命名类别而不触及博客文章文档,允许多个具有相同名称的类别(例如,每个子类别用于不同的主题或者因为不同的用户使用相同的系统,每个用户都创建自己的类别层次结构.也可能没有类别的规范名称,例如,当存在多个翻译时. (2认同)