单个表或多个表用于层次结构数据

Tri*_*Nhu 5 database django postgresql database-design

我必须实现以下层次结构数据:

Category (id, name, url)
SubCategory (id, name, url)
SubSubCategory (id, name, url)
Run Code Online (Sandbox Code Playgroud)

注意,这是多对多关系。EG:每个节点可以有多个父级或子级。没有流通关系(感谢上帝)。只有某些SubSubCategory可以属于多个SubCategory。

我的实现:为此,我使用单个表

Cat (id, type(category, subcategory, subsubcategory), name, url)
CatRelation (id, parent_id, child_id, pre_calculated_index for tree retrieval)
Run Code Online (Sandbox Code Playgroud)

pre_calculated_index可以左,右实施改性序树遍历的[1 2]或在我的实现的路径。这pre_calculated_index是在将子级添加到一个节点时计算出来的,这样,当您检索一棵树时,只需要按此字段排序即可,而不必进行递归查询。

无论如何,我的老板认为这种实现方式并不理想。他建议为每种类型的类别都拥有每个表,然后有一个数据透视表来链接它们:

Category (id, name, url)
SubCategory (id, name, url)
SubSubCategory (id, name, url)
Category_SubCategory(category_id, sub_category_id)
SubCategory_SubSubCategory(sub_category_id, sub_sub_category_id)
Run Code Online (Sandbox Code Playgroud)

检索树时,只需联接所有表。他的论据是,稍后将某些属性添加到不需要的任何类别类型时,在单表实现中将字段设为null。并且pre_calculated_index可能会出错,因为它是用代码计算的。

我应该跟随哪一个?哪个有更好的表现?

我使用django和postgreSQL。

PS:有关pre_calculated_index实现的更多详细信息:我在CatRelation中添加了一个路径(字符串,唯一,索引)值(而不是每个节点左右):根节点将具有“ path ='”。子节点添加到CatRelation后,将具有path = parent_path +'。因此,当您按此路径排序时,将按树顺序获得所有内容。例子:

Cat

| id | name       | url |
|----|------------|-----|
| 1  | Cat1       |     |
| 2  | Subcat1    |     |
| 3  | Subcat2    |     |
| 4  | Subcat3    |     |
| 5  | Subsubcat1 |     |
| 6  | Subsubcat2 |     |
| 7  | Subsubcat3 |     |

CatRelationship                                        Left right equivalent

| id    | parent_id     | child_id  | path      |           |lft |rght|            
|----   |-----------    |---------- |--------   |           |----|----|
| 1     | null          | 1         | 1.        |           | 1  | 14 |
| 2     | 1             | 2         | 1.2.      |           | 2  | 3  |
| 3     | 1             | 3         | 1.3.      |           | 4  | 11 |
| 4     | 1             | 4         | 1.4.      |           | 12 | 13 |
| 5     | 3             | 5         | 1.3.5.    |           | 5  | 6  |
| 6     | 3             | 6         | 1.3.6.    |           | 7  | 8  |
| 7     | 3             | 7         | 1.3.7.    |           | 9  | 10 |
Run Code Online (Sandbox Code Playgroud)

因此,当您按路径排序(或在修改后的预排序树中按左排序)时,您将获得以下不错的树结构而无需递归:

| id    | parent_id     | child_id  | path      |
|----   |-----------    |---------- |--------   |
| 1     | null          | 1         | 1.        |
| 2     | 1             | 2         | 1.2.      |
| 3     | 1             | 3         | 1.3.      |
| 5     | 3             | 5         | 1.3.5.    |
| 6     | 3             | 6         | 1.3.6.    |
| 7     | 3             | 7         | 1.3.7.    |
| 4     | 1             | 4         | 1.4.      |
Run Code Online (Sandbox Code Playgroud)

而且我总是可以使用递归动态构建路径:

WITH RECURSIVE CTE AS (
    SELECT R1.*, CONCAT(R1.id, ".") AS dynamic_path 
    FROM CatRelation AS R1
    WHERE R1.child_id = request_id
UNION ALL
    SELECT R2.*, CONCAT(dynamic_path, R2.child_id, ".") AS dynamic_path
    FROM CTE 
    INNER JOIN CatRelation AS R2 ON (CTE.child_id = R2.parent_id)      
)
SELECT * FROM CTE;
Run Code Online (Sandbox Code Playgroud)

这不是有人建议的继承

cez*_*zar 4

您的问题有些固执己见,因为您要求比较两种不同的方法。我会尝试提供一个答案,尽管恐怕没有唯一的真实答案。在答案的其余部分中,我将把您的方法称为解决方案 A,将您老板建议的方法称为解决方案 B。

我强烈建议遵循你老板提出的方法:

  • 因为他是你的老板!如果以后出了什么问题,没有人可以责怪你。您已按照说明进行操作。
  • 因为它遵循“Python之禅”

特别适用 Python 之禅的以下规则:

  • 显式的比隐式的好。
    解决方案B非常明确。解A是隐式的。
  • 简单总比复杂好。
    解决方案B非常简单明了。解A很复杂。
  • 稀疏比密集好。
    解 B 是稀疏的。解决方案 A 很密集,对用户隐藏了显而易见的内容。
  • 可读性很重要。
    解决方案 B 非常冗长,但易于阅读。解决方案A需要更多的时间和精力来理解。

你可能会以美元来衡量绩效ms,但你的老板最终会以美元来衡量绩效。让初级开发人员加入解决方案 B 所需的时间要少得多。时间对于企业来说是昂贵的。

模型的未来变化可以更容易地实现。如果您想添加另一个Category不应该(或不需要)出现在SubCategoryand中的字段,该怎么办SubSubCategory

使用解决方案 B 测试(单元和功能)要容易得多。它最终需要更多行代码并且更冗长,但更容易阅读和理解。

性能会有所不同,具体取决于用例。数据库中有多少条记录?更关键的是:检索还是插入/更新?使早期性能更高的因素可能会降低后者的性能,反之亦然。

我希望你听过这句话:

过早的优化是万恶之源。

由唐纳德·高德纳 (Donald Knuth) 提供。

当出现具体问题时,您会关心性能。这并不意味着您在设计应用程序时不应该对性能进行任何深思熟虑。

您可以缓存查询,一个选项是使用redis. 既然你使用了,PostgreSQL你也可以使用物化视图。但正如我所说,当我到达那座桥时,我会跨过那座桥。

编辑:您没有提到任何其他模型。我假设当你有类别时,你会有一些实体,比如说分类在这些类别中的产品,即分类的产品。这里我举个例子:

  • 类别: 男士
  • 子类别: 运动服
  • 子子类别: 跑鞋
  • 产品:ACME speedVX13(虚构品牌和型号)

如果您严格遵循此层次结构并仅将产品放入 SubSubCategory 中,那么解决方案 B 更好。

但是,如果您有一个虚构的产品Sportskit ACME(跑鞋、短裤和无袖衬衫),您无法将其放入 SubSubCategory 中,并且需要跳过一级将其放入 SubCategory 中,那么您最终可能会使用类似genericrelations的东西。
在这种情况下,解决方案 A 更好。