Recursive CTEs
Hierarchies and graph walks with cycle guards. Production-oriented PostgreSQL guidance.
Recipe
WITH RECURSIVE tree AS (
SELECT id, parent_id, 1 AS depth FROM app.nodes WHERE parent_id IS NULL
UNION ALL
SELECT n.id, n.parent_id, t.depth + 1 FROM app.nodes n JOIN tree t ON n.parent_id = t.id
)
SELECT * FROM tree;When to reach for this: You implement or review SQL using this concept in PostgreSQL 18.
Working Example
CREATE TABLE IF NOT EXISTS app.nodes (id int PRIMARY KEY, parent_id int REFERENCES app.nodes(id));What this demonstrates:
- Idiomatic PostgreSQL 18 SQL
- Patterns suitable for EXPLAIN review
- Safe defaults for OLTP workloads
Deep Dive
Mechanism
- Parser builds query tree.
- Executor runs plan nodes.
- MVCC provides snapshot isolation per statement or transaction.
Practice
- Keep examples readable.
- Test at realistic row counts.
Gotchas
- Long transactions - Block vacuum and increase bloat.. Fix: Commit quickly; set timeouts.
- Missing indexes - Seq scans under load.. Fix: Index join and filter columns.
- Implicit locks - Surprise blocking.. Fix: Use explicit FOR UPDATE when needed.
- Wrong isolation assumption - Anomalies under concurrent writes.. Fix: Pick level deliberately.
- Untested migrations - DDL lock or invalid objects.. Fix: Stage migrations with production-like data.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Application-level logic | Simple CRUD | Integrity spread across services |
| Materialized view | Heavy repeated reads | Refresh complexity |
| External stream processor | Cross-system events | Extra operational surface |
FAQs
Does this apply to replicas?
Read-only replicas execute SELECT; writes go to primary.
What about PgBouncer?
Transaction pooling affects session-level features; use session mode when needed.
Related
- Advanced SQL Basics - overview
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.