Modeling Hierarchical Data Structures

1. Using Adjacency List Model

AspectDetail
Schemaid, parent_id (self-FK)
Insert/MoveO(1) — just update parent_id
Read treeRecursive CTE or app loop
Best forFrequent writes, shallow trees

Example: Adjacency List

CREATE TABLE categories (
  id BIGSERIAL PRIMARY KEY,
  name TEXT, parent_id BIGINT REFERENCES categories(id)
);
WITH RECURSIVE t AS (
  SELECT id, name, parent_id, 1 AS depth FROM categories WHERE id = 1
  UNION ALL
  SELECT c.id, c.name, c.parent_id, t.depth+1
  FROM categories c JOIN t ON c.parent_id = t.id
) SELECT * FROM t;

2. Using Nested Set Model

AspectDetail
Schemaid, lft, rgt
DescendantsWHERE lft BETWEEN p.lft AND p.rgt
ReadO(log n) — fast subtree queries
WriteExpensive — re-numbering many rows
Best forMostly-read trees

3. Using Path Enumeration Model

AspectDetail
Schemapath TEXT (e.g., '/1/4/12/')
DescendantsWHERE path LIKE '/1/4/%'
AncestorsSplit path into IDs
Postgres ltreeNative tree type with operators
Best forRead-heavy, deep trees

4. Using Closure Table Model

AspectDetail
Schemanodes + closure(ancestor, descendant, depth)
Insert subtreeInsert rows for every ancestor pair
Query speedO(1) descendants/ancestors via JOIN
StorageO(n²) worst case
Best forComplex queries, multiple parents (DAGs)

Example: Closure Table

CREATE TABLE node (id BIGSERIAL PRIMARY KEY, name TEXT);
CREATE TABLE node_closure (
  ancestor   BIGINT REFERENCES node(id),
  descendant BIGINT REFERENCES node(id),
  depth      INT NOT NULL,
  PRIMARY KEY (ancestor, descendant)
);
-- All descendants of node 1:
SELECT n.* FROM node_closure c JOIN node n ON n.id = c.descendant
WHERE c.ancestor = 1 AND c.depth > 0;

5. Comparing Hierarchical Models

ModelInsertSubtree ReadMoveStorage
Adjacency ListO(1)Recursive CTEO(1)Compact
Nested SetO(n)Range scanExpensiveCompact
Path EnumerationO(1)Prefix scanUpdate subtreeMedium
Closure TableO(depth)JOIN O(1)Rebuild edgesO(n²)

6. Querying Hierarchical Data

NeedPattern
All descendantsRecursive CTE / closure JOIN / LIKE on path
All ancestorsWalk parent_id up; or closure
Depth / levelRecursion counter or stored depth
SiblingsWHERE parent_id = N AND id <> self

7. Modeling Tree Structures

Tree TypeBest Model
Category trees (shallow)Adjacency list
File systemsPath enumeration
Org chartsClosure table
Comment threadsPath / ltree

8. Modeling Graph Structures

AspectDetail
Edges table(from_id, to_id, edge_type, weight)
Multi-parentClosure table or recursive CTE
CyclesTrack visited set; bounded recursion depth
Performance limitsSwitch to graph DB (Neo4j) for deep traversals
Apache AGE (PG)Cypher inside Postgres

9. Handling Hierarchical Data Updates

OperationDetail
Move subtreeAdjacency: change parent_id; closure: rebuild edges
Delete subtreeON DELETE CASCADE / recursive purge
Prevent cyclesBEFORE trigger checks new parent not in descendants
Atomic moveWrap in transaction

10. Optimizing Hierarchical Queries

OptimizationDetail
Index parent_idCritical for downward traversal
Index path / lft / rgtPer model
Materialize depthAvoid recomputing
Cache hot subtreesEspecially navigation menus
Limit recursion depthBounded recursive CTE