I must implement this following hierarchy data:
Category (id, name, url)
SubCategory (id, name, url)
SubSubCategory (id, name, url)
NOTE that this is many-to-many relationship. EG: Each node can have multiple parents or children. There will be no circulation relationship (thank GOD). Only some SubSubCategory may belong to multiple SubCategory.
My implementation: I use single table for this
Cat (id, type(category, subcategory, subsubcategory), name, url)
CatRelation (id, parent_id, child_id, pre_calculated_index for tree retrieval)
pre_calculated_index
can be left right implementation of modified preorder tree traversal [1, 2] or a path in my implementation. This pre_calculated_index
is calculated when adding child to one node so that when you retrieve a tree you only need to sort by this field and avoid recursive query.
Anyway my boss argued that this implementation is not ideal. He suggests having each table for each type of category, and then have a pivot tables to link them:
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)
When you retrieve a tree, you only need to join all tables. His arguments is that later when you add some attribute to any category type you don't need and null field in single table implementation. And the pre_calculated_index
may get wrong since it is calculated in code.
Which one should I follow? Which has better performance?
I use django and postgreSQL.
PS: More detail on my pre_calculated_index
implementation:
Instead of left and right for each node I add a path (string, unique, indexed) value to the CatRelation: root node will have `path = '.'
child node when added to CatRelation will have path = parent_path + '.' So when you sort by this path, you get everything in tree order. Examples:
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 |
So when you sort by path (or order by left in modified preorder tree), you will got this nice tree structure without recursion:
| 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. |
And I can always build path dynamically using recursion:
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;
This is not inheritance as someone suggested