DOCODIVE
Intermediate Free Learning Path

Databases Intermediate: Advanced SQL & NoSQL

Master the databases behind production systems — window functions, CTEs, query optimization, MongoDB, Redis, replication, sharding, and caching. Build databases that scale.

6–8 weeks 30 lessons 1 capstone Databases Beginner required
Start Learning
01

Subqueries

22 min
What you'll learn
  • Write nested queries
  • Use subqueries in WHERE/FROM
  • Compare with joins

A subquery is a SELECT inside another query. In WHERE, it filters using another query's result (find users with above-average salary). In FROM, it acts as a temporary table. Subqueries are readable and solve problems joins can't express easily — like comparing against an aggregate. The trade-off: they can be slower than joins, so use them for clarity first, optimize later.

subquery.sql
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
Live Preview
query result — Subqueries
name
salary
Sara
95000
Omar
88000
💡 Tip: Subqueries shine for 'compare against an aggregate' — if it reads naturally, write it that way first.
Try it yourself

Write a subquery to find products more expensive than the average price.

WHERE price > (SELECT AVG(price)...).
SELECT name FROM products WHERE price > (SELECT AVG(price) FROM products);
02

Common Table Expressions (CTEs)

22 min
What you'll learn
  • Write WITH clauses
  • Break complex queries into steps
  • Use recursive CTEs

A CTE (WITH clause) names a temporary result set, making complex queries readable step by step. Instead of nesting subqueries, you define named CTEs and reference them. Recursive CTEs even handle hierarchical data (org charts, category trees) that plain SQL can't. CTEs are the modern standard for readable, maintainable complex SQL.

cte.sql
WITH high_earners AS (
    SELECT * FROM employees WHERE salary > 80000
)
SELECT department, COUNT(*) AS count
FROM high_earners
GROUP BY department;
Live Preview
query result — Common Table Expressions (CTEs)
WITH high_earners AS (...)
SELECT ... FROM high_earners
✓ Best Practice: CTEs turn spaghetti SQL into named, testable steps — always prefer WITH over nested subqueries.
Try it yourself

What does WITH define?

Named result.
A named temporary result set (CTE) usable in the main query.
03

Window Functions

26 min
What you'll learn
  • Use OVER() clause
  • Learn ROW_NUMBER, RANK, LAG
  • Compute running totals

Window functions compute values ACROSS rows without collapsing them — unlike GROUP BY. ROW_NUMBER ranks rows, LAG/LEAD access previous/next rows, and SUM() OVER() gives running totals. The OVER(PARTITION BY ... ORDER BY ...) clause defines the 'window'. Window functions are the most powerful SQL feature for analytics — rankings, moving averages, period-over-period comparison.

window.sql
SELECT
  name, salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) AS rank,
  SUM(salary) OVER (ORDER BY salary) AS running_total
FROM employees;
Live Preview
query result — Window Functions
name
salary
rank
Sara
95000
1
Omar
88000
2
🔎 Important: Window functions keep every row (unlike GROUP BY) while computing across-row analytics — the key difference.
Try it yourself

Which function gives a row's rank within a partition?

Ranking.
RANK() or ROW_NUMBER().
04

Advanced Joins (Self, Cross, Full Outer)

24 min
What you'll learn
  • Master all join types
  • Use self-joins for hierarchies
  • Know when to use each

Beyond INNER/LEFT, advanced joins: SELF JOIN (join a table to itself — find employees and their managers), CROSS JOIN (every combination — Cartesian product), FULL OUTER JOIN (all rows from both, matched where possible). Knowing all join types lets you express any relationship. Self-joins power org charts; full outer joins find unmatched data on both sides.

joins.sql
-- Self join: employees and their managers
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
Live Preview
query result — Advanced Joins (Self, Cross, Full Outer)
employees e
employees m
💡 Tip: Self-join = alias the same table twice — the database treats them as two separate tables.
Try it yourself

Which join returns rows from both tables even when unmatched?

Both sides.
FULL OUTER JOIN.
05

Stored Procedures

24 min
What you'll learn
  • Create reusable SQL routines
  • Use parameters
  • Encapsulate business logic

A stored procedure is a saved block of SQL you can call repeatedly with parameters — like a function for your database. They encapsulate business logic (complex multi-step operations), reduce network traffic, and improve consistency. Instead of sending 10 queries, you call one procedure. They're controversial (some prefer logic in app code), but essential to know for legacy and enterprise systems.

procedure.sql
CREATE PROCEDURE transfer_money(
    from_id INT, to_id INT, amount DECIMAL
)
LANGUAGE SQL
AS $$
    UPDATE accounts SET balance = balance - amount WHERE id = from_id;
    UPDATE accounts SET balance = balance + amount WHERE id = to_id;
$$;

CALL transfer_money(1, 2, 100);
Live Preview
query result — Stored Procedures
CALL
transfer_money(1, 2, 100)
🔎 Important: Stored procedures move business logic INTO the database — powerful, but weigh it against keeping logic in application code.
Try it yourself

What's a benefit of stored procedures?

Reuse.
Reusable, parameterized SQL logic executed server-side.
06

Triggers

24 min
What you'll learn
  • Automate actions on data changes
  • Use BEFORE/AFTER triggers
  • Audit and enforce rules

A trigger automatically runs when data changes (INSERT/UPDATE/DELETE). Use them for auditing (log who changed what), enforcing complex rules, and maintaining derived data. Example: automatically update an 'updated_at' timestamp before every row update. Triggers run inside the database, guaranteeing they always fire — regardless of which app writes the data.

trigger.sql
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();
Live Preview
query result — Triggers
UPDATE
TRIGGER
timestamp set
💡 Tip: Triggers guarantee business rules fire even if a rogue app writes directly — the database enforces them.
Try it yourself

When does a BEFORE UPDATE trigger fire?

Before.
Just before the row is updated — can modify the new values.
07

Transaction Isolation Levels

26 min
What you'll learn
  • Understand isolation levels
  • Prevent dirty reads and phantoms
  • Balance consistency vs performance

Isolation levels control how much concurrent transactions interfere. Read Uncommitted (fastest, dirtiest), Read Committed (default), Repeatable Read, Serializable (strictest, slowest). Higher isolation = fewer anomalies (dirty reads, non-repeatable reads, phantoms) but more blocking. Choosing the right level is a real trade-off between consistency and throughput.

isolation.sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- ... concurrent-safe operations ...
COMMIT;
Live Preview
query result — Transaction Isolation Levels
RU
RC
RR
SER
🔎 Important: Higher isolation = safer but slower. Serializable prevents ALL anomalies but serializes access.
Try it yourself

What does 'dirty read' mean?

Uncommitted.
Reading another transaction's uncommitted (dirty) changes.
08

Advanced Indexing Strategies

26 min
What you'll learn
  • Choose the right index types
  • Use composite indexes
  • Avoid index mistakes

Advanced indexing: composite indexes (multiple columns) for multi-column queries, covering indexes (include all needed columns), partial indexes (where clause), and unique indexes (constraint + speed). The key insight: column ORDER in a composite index matters — put the most selective column first. Bad indexing is the #1 cause of slow production databases.

index.sql
CREATE INDEX idx_orders_customer_date
ON orders(customer_id, order_date);

-- This query uses the composite index
SELECT * FROM orders
WHERE customer_id = 42 AND order_date > '2026-01-01';
Live Preview
query result — Advanced Indexing Strategies
(customer_id, order_date)
index hit
✓ Best Practice: Composite index column order matters — the leftmost column must appear in WHERE for the index to help.
Try it yourself

A composite index on (a, b) helps queries filtering on which columns?

Leftmost.
a alone, or a and b together — but not b alone.
09

Query Execution Plans (EXPLAIN)

28 min
What you'll learn
  • Read EXPLAIN output
  • Find slow queries
  • Optimize based on plans

EXPLAIN shows HOW the database executes a query — which indexes it uses, whether it scans or seeks, estimated costs. It's the single most important tool for query optimization. Learn to read it: look for 'Seq Scan' (full table scan = slow, needs index) vs 'Index Scan' (fast). EXPLAIN turns guesswork into evidence-based optimization.

explain.sql
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 42;
Live Preview
query result — Query Execution Plans (EXPLAIN)
Seq Scan on orders (rows=500)
⚠ full scan
🔎 Important: Seq Scan = full table scan = likely slow. Index Scan = using an index = likely fast. Read the plan before optimizing.
Try it yourself

What does 'Seq Scan' tell you?

Full scan.
The database is scanning every row — likely missing an index.
10

Query Optimization Techniques

28 min
What you'll learn
  • Write fast SQL
  • Avoid common anti-patterns
  • Optimize systematically

Optimization rules: SELECT only needed columns (not *), avoid functions on indexed columns in WHERE, use EXISTS instead of IN for large sets, avoid OR with indexes (use UNION), paginate with keyset not OFFSET. Most slow queries stem from a few anti-patterns. Learning these transforms a 10-second query into milliseconds.

optimize.sql
-- BAD: function on indexed column prevents index use
SELECT * FROM users WHERE LOWER(email) = '[email protected]';

-- GOOD: index-friendly comparison
SELECT * FROM users WHERE email = '[email protected]';
Live Preview
query result — Query Optimization Techniques
LOWER(email) = ?
email = ?
✓ Best Practice: Never wrap an indexed column in a function — it disables the index and forces a scan.
Try it yourself

Why avoid SELECT * in production queries?

Unneeded data.
It transfers unneeded columns, wasting bandwidth and preventing index-only scans.
11

Concurrency Control & Locking

26 min
What you'll learn
  • Understand locks
  • Prevent deadlocks
  • Handle concurrent access

Concurrent transactions need locking to stay consistent — shared locks (readers) and exclusive locks (writers). Too much locking = blocking; too little = data corruption. Deadlocks happen when two transactions each hold a lock the other needs. Prevention: consistent lock ordering, short transactions, and retry logic. This is production database discipline.

locking.sql
-- Deadlock scenario: two transactions lock opposite order
-- T1: UPDATE accounts SET ... WHERE id=1; UPDATE ... id=2
-- T2: UPDATE accounts SET ... WHERE id=2; UPDATE ... id=1
-- Both wait on each other = deadlock
Live Preview
query result — Concurrency Control & Locking
T1 holds A, wants B
T2 holds B, wants A
= deadlock
⚠️ Common Mistake: Acquire locks in a CONSISTENT order to prevent deadlocks — it's the simplest and most effective rule.
Try it yourself

What's a deadlock?

Mutual wait.
Two transactions each waiting for a lock the other holds.
12

Partitioning Tables

24 min
What you'll learn
  • Partition large tables
  • Improve query performance
  • Manage huge datasets

Partitioning splits a huge table into smaller physical pieces by range (by date), list (by category), or hash. Queries filtering on the partition key scan only relevant partitions — massive speedup on billion-row tables. It also simplifies archiving old data (drop a partition vs delete millions of rows). Essential for time-series and log data at scale.

partition.sql
CREATE TABLE orders (
    id INT, order_date DATE, total DECIMAL
) PARTITION BY RANGE (order_date);

CREATE TABLE orders_2026 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
Live Preview
query result — Partitioning Tables
2024
2025
2026
🔎 Important: Partitioning = divide and conquer for huge tables — queries touch only relevant partitions.
Try it yourself

Best partition key for log data?

Time.
By date (range partitioning) — queries filter on time.
13

Database Migrations

20 min
What you'll learn
  • Version your schema
  • Apply changes safely
  • Use migration tools

Migrations version-control your database schema — each change (add column, create table) is a versioned file applied in order. Tools like Alembic (Python) track applied migrations, support rollbacks, and make schema changes reproducible across environments. Without migrations, schema changes are manual, untracked, and error-prone across dev/staging/production.

migration.py
# Alembic migration
# def upgrade():
#     op.add_column('users', sa.Column('age', sa.Integer()))
# def downgrade():
#     op.drop_column('users', 'age')
Live Preview
query result — Database Migrations
v1 → v2 → v3
versioned
✓ Best Practice: Treat schema like code — version it with migrations. Manual schema changes cause production incidents.
Try it yourself

Why version database schema?

Reproduce.
To reproduce changes across environments and roll back safely.
14

Advanced Aggregation (HAVING, ROLLUP)

22 min
What you'll learn
  • Filter groups with HAVING
  • Create subtotals with ROLLUP
  • Master advanced grouping

HAVING filters groups (after GROUP BY) — WHERE filters rows before grouping. ROLLUP/CUBE generate subtotals and grand totals for reporting. This is how dashboards produce hierarchical summaries in one query. Mastering HAVING + ROLLUP turns raw data into decision-ready reports directly in SQL.

having.sql
SELECT department, SUM(salary) AS total
FROM employees
GROUP BY department
HAVING SUM(salary) > 200000
ORDER BY total DESC;
Live Preview
query result — Advanced Aggregation (HAVING, ROLLUP)
410000
Engineering
260000
Sales
💡 Tip: WHERE filters rows, HAVING filters groups — the order is FROM→WHERE→GROUP BY→HAVING.
Try it yourself

HAVING vs WHERE — when each?

Groups vs rows.
WHERE filters individual rows; HAVING filters aggregated groups.
15

MongoDB Basics (Document Store)

24 min
What you'll learn
  • Understand document databases
  • Use MongoDB CRUD
  • Compare with relational

MongoDB stores data as JSON-like documents in collections — schemaless, flexible, and naturally matching objects in code. No migrations needed for new fields; each document can differ. It's the most popular NoSQL database, ideal for rapid development and unstructured data. The mental model: collections (like tables) of documents (like rows, but flexible).

mongo.js
db.users.insertOne({
    name: 'Ali',
    email: '[email protected]',
    tags: ['python', 'sql'],
    address: { city: 'Lahore' }
});
db.users.find({ 'address.city': 'Lahore' });
Live Preview
query result — MongoDB Basics (Document Store)
🍃 document
💡 Tip: MongoDB's flexibility = no schema migrations. It matches how objects look in code, not how tables force you to structure.
Try it yourself

What does MongoDB store data as?

JSON-like.
JSON-like BSON documents in collections.
16

Redis Basics (Key-Value Store)

22 min
What you'll learn
  • Understand in-memory caching
  • Use Redis data types
  • Speed up applications

Redis is an in-memory key-value store — blazingly fast because data lives in RAM. It's used for caching (store frequent query results), sessions, rate limiting, and pub/sub messaging. Data types: strings, hashes, lists, sets, sorted sets. The killer use: cache database results in Redis, and your app gets 100x faster reads.

redis.py
import redis
r = redis.Redis()
r.set('user:42', 'Ali', ex=3600)  # cache with 1-hour expiry
name = r.get('user:42')  # O(1) read
print(name)
Live Preview
query result — Redis Basics (Key-Value Store)
⚡ in-memory
✓ Best Practice: Cache first, database second — Redis absorbs repeated reads so your DB only handles what it must.
Try it yourself

Redis's main advantage?

Memory.
In-memory speed — O(1) operations, microseconds latency.
17

NoSQL Data Modeling

24 min
What you'll learn
  • Model for document stores
  • Embed vs reference
  • Design for access patterns

NoSQL modeling inverts relational thinking: you model for QUERIES, not normalization. Embed related data (order with its items) when read together; reference when data is shared or unbounded. The guiding question: how will this data be accessed? Denormalization is fine — duplicate for read speed. Access patterns drive the entire design.

model.js
// Embed: order with items (read together)
{
  _id: 1,
  customer: 'Ali',
  items: [
    { product: 'Book', qty: 2 },
    { product: 'Pen', qty: 5 }
  ],
  total: 30
}
Live Preview
query result — NoSQL Data Modeling
{ order, items: [...] }

embedded — one read

🔎 Important: NoSQL: duplicate for reads, normalize only when data is shared or unbounded. Access patterns decide.
Try it yourself

When should you embed vs reference?

Read together.
Embed when read together; reference when shared or unbounded.
18

Caching Strategies

22 min
What you'll learn
  • Design cache layers
  • Learn cache-aside pattern
  • Handle cache invalidation

Caching stores frequently-read data in fast storage (Redis). Cache-aside: check cache → hit? return : miss? query DB, store in cache, return. The hard part is invalidation — when data changes, the cache must update or go stale. Strategies: TTL (expiry), write-through, write-behind. Caching is the highest-leverage performance optimization for read-heavy apps.

cache.py
def get_user(user_id):
    cached = r.get(f'user:{user_id}')
    if cached: return cached  # cache hit
    user = db.query(user_id)  # cache miss
    r.set(f'user:{user_id}', user, ex=300)  # store 5 min
    return user
Live Preview
query result — Caching Strategies
cache hit?
miss →
DB → cache
✓ Best Practice: Cache invalidation is the hard part — TTL is your friend for eventually-fresh data.
Try it yourself

What's cache-aside?

Check then load.
Check cache, on miss load from DB and populate cache.
19

CAP Theorem

24 min
What you'll learn
  • Understand consistency/availability/partition tolerance
  • Know why you can only pick two
  • Choose databases wisely

The CAP theorem: in a distributed system, you can guarantee only two of Consistency, Availability, and Partition tolerance. During a network partition, you choose: stay consistent (reject some requests) or stay available (serve possibly stale data). Most systems choose AP (NoSQL like Cassandra) or CP (some SQL setups). This is why 'perfect' distributed databases don't exist.

cap.py
# CAP: pick two of three
# - Consistency: all nodes see same data
# - Availability: every request gets a response
# - Partition tolerance: system works despite network splits
print('You can only guarantee two')
Live Preview
query result — CAP Theorem
C
A
P
pick two
🔎 Important: CAP explains why distributed databases trade consistency for availability — know your system's choice.
Try it yourself

Which two does a typical NoSQL system choose?

Availability.
Availability + Partition tolerance (AP) — eventual consistency.
20

Horizontal vs Vertical Scaling

20 min
What you'll learn
  • Understand scaling approaches
  • Know sharding basics
  • Scale databases effectively

Vertical scaling: bigger machine (more RAM/CPU) — simple but has limits. Horizontal scaling: more machines — complex but unlimited. For databases, horizontal = sharding (split data across servers by a shard key) or replication (copies for reads). Most modern systems scale horizontally for reliability and cost. The trade-off: distributed complexity vs single-machine simplicity.

scaling.py
# Vertical: add RAM/CPU to one server
# Horizontal: shard by user_id across 4 servers
#   server_0: user_id % 4 == 0
#   server_1: user_id % 4 == 1
#   server_2: user_id % 4 == 2
#   server_3: user_id % 4 == 3
Live Preview
query result — Horizontal vs Vertical Scaling
server_0
server_1
server_2
💡 Tip: Scale vertically until you can't, then horizontally. Sharding is the price of unlimited scale.
Try it yourself

What's sharding?

Split data.
Splitting data across multiple servers by a shard key.
21

Replication (Master-Slave)

24 min
What you'll learn
  • Understand replication
  • Set up read replicas
  • Improve availability and scale

Replication copies data from a primary (master) to replicas (slaves). Reads scale across replicas; writes go to the primary. It also provides failover — if the primary dies, a replica promotes. This is how production databases achieve read scalability AND high availability. The trade-off: replicas are eventually consistent (slight lag behind the primary).

replication.sql
# Primary: handles writes
# Replica 1: handles reads (customers A-M)
# Replica 2: handles reads (customers N-Z)
# If primary fails → promote a replica to primary
Live Preview
query result — Replication (Master-Slave)
Primary
Replica
Replica
🔎 Important: Replication = read scaling + high availability. Writes stay on one primary; reads fan out to replicas.
Try it yourself

Why can read replicas be slightly stale?

Sync lag.
Asynchronous replication has lag — replicas trail the primary slightly.
22

Connection Pooling

20 min
What you'll learn
  • Understand database connections cost
  • Use connection pools
  • Prevent connection exhaustion

Opening a database connection is expensive. A connection pool maintains a set of reusable connections, so app requests grab one, use it, return it — no repeated open/close overhead. Without pooling, apps exhaust connections under load. PgBouncer (PostgreSQL) and built-in poolers in frameworks solve this. Essential for any web app with a database.

pooling.py
# PgBouncer config: pool 20 connections
# 100 app requests share 20 DB connections
# Requests queue when all busy — no exhaustion
Live Preview
query result — Connection Pooling
100 requests
20 connections
✓ Best Practice: Always use connection pooling in production — otherwise 'too many connections' will crash you under load.
Try it yourself

Why pool database connections?

Expensive open.
Opening connections is costly; pooling reuses them, preventing exhaustion.
23

Database Monitoring & Health

22 min
What you'll learn
  • Monitor database health
  • Track key metrics
  • Proactively prevent issues

Production databases need monitoring: CPU, memory, disk I/O, connection count, slow queries, replication lag, cache hit ratio. Tools: pg_stat_statements, Prometheus + Grafana, cloud monitors. Key metrics reveal problems before they become outages. A monitored database is a reliable one; an unmonitored one is a ticking time bomb.

monitor.sql
-- PostgreSQL: find slow queries
SELECT query, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
Live Preview
query result — Database Monitoring & Health
0.8s
Slow query
45
Connections
🔎 Important: Monitor slow queries, connections, and replication lag — the big three that predict outages.
Try it yourself

Name two critical database metrics.

Health.
Slow queries, connection count, replication lag, cache hit ratio.
24

Full-Text Search

24 min
What you'll learn
  • Search text efficiently
  • Use full-text indexes
  • Handle ranking

Full-text search finds documents by relevance, not just exact match — handling stemming ('running' matches 'run'), ranking, and multiple words. PostgreSQL has built-in full-text search (tsvector, tsquery); Elasticsearch is the dedicated search engine. Unlike LIKE '%word%', full-text search is indexed and relevance-ranked — the difference between 'find' and 'search'.

fts.sql
CREATE INDEX idx_fts ON articles USING GIN(to_tsvector('english', content));

SELECT title
FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('database & performance');
Live Preview
query result — Full-Text Search
'database & performance'
ranked matches
💡 Tip: LIKE '%term%' does a slow full scan. Full-text search uses indexes and understands language — order of magnitude faster.
Try it yourself

Full-text search vs LIKE — which is faster and why?

Index.
Full-text — it's indexed and handles language; LIKE does full scans.
25

Database Security Best Practices

22 min
What you'll learn
  • Secure database access
  • Use least privilege
  • Encrypt sensitive data

Database security: least privilege (app accounts can only do what they need), parameterized queries (SQL injection), encryption at rest and in transit, strong authentication, audit logging, and regular patching. The principle: an attacker with your app credentials shouldn't own your entire database. Defense in depth — multiple layers, no single point.

security.sql
-- Least privilege: app account can only query, not drop
GRANT SELECT, INSERT, UPDATE ON orders TO app_user;
REVOKE DROP, DELETE ON orders FROM app_user;
Live Preview
query result — Database Security Best Practices
GRANT
least privilege
⚠️ Common Mistake: Least privilege is the #1 security principle — give app accounts only what they need, nothing more.
Try it yourself

What's least privilege?

Minimum access.
Granting only the minimum permissions needed for a task.
26

Time-Series Databases (InfluxDB)

22 min
What you'll learn
  • Understand time-series data
  • Use InfluxDB or TimescaleDB
  • Optimize for timestamps

Time-series databases are optimized for timestamped data — sensor readings, metrics, logs, stock prices. They excel at: high write throughput (millions of points/second), time-based queries (last hour, downsampling), retention policies (auto-delete old data). InfluxDB is the dedicated choice; TimescaleDB adds time-series to PostgreSQL. If your data is overwhelmingly timestamped, a TSDB beats a relational DB.

timeseries.sql
-- TimescaleDB (PostgreSQL extension)
CREATE TABLE metrics (
    time TIMESTAMPTZ NOT NULL,
    device_id INT,
    temperature DOUBLE PRECISION
);
SELECT create_hypertable('metrics', 'time');
Live Preview
query result — Time-Series Databases (InfluxDB)
time-stamped data
hypertable
💡 Tip: If your primary query pattern is 'data from the last X time', use a time-series database.
Try it yourself

Time-series DBs optimize for what query pattern?

Time ranges.
Time-range queries and high-volume timestamped writes.
27

SQLAlchemy ORM Advanced

24 min
What you'll learn
  • Use relationships
  • Write complex ORM queries
  • Understand eager loading

Advanced ORM: relationships (one-to-many, many-to-many) map to Python attributes — user.orders returns related rows. Eager loading (joinedload, selectinload) avoids the N+1 query problem (one query per row). Knowing ORM advanced features means you write clean Python while the ORM generates efficient SQL. The N+1 problem is the classic ORM performance trap.

orm_adv.py
from sqlalchemy.orm import relationship, joinedload

class User(Base):
    orders = relationship('Order', back_populates='user')

users = session.query(User).options(joinedload(User.orders)).all()
for u in users:
    print(u.name, len(u.orders))  # no N+1 — orders eager-loaded
Live Preview
query result — SQLAlchemy ORM Advanced
joinedload
=
no N+1
✓ Best Practice: N+1 queries are the classic ORM trap — eager loading (joinedload) fixes it with one query.
Try it yourself

What's the N+1 query problem?

One per row.
One query for the list, then one extra query per row for related data.
28

Database-Backed App Architecture

24 min
What you'll learn
  • Design app-database interaction
  • Separate concerns
  • Build scalable data layers

Production app architecture separates layers: controllers → services → repositories → database. Repositories encapsulate all SQL (no queries scattered in views). The database is behind an API of repository functions, making it swappable, testable, and consistent. This clean separation is the difference between a prototype and a maintainable system.

architecture.py
class UserRepository:
    def __init__(self, session):
        self.session = session
    def get_by_email(self, email):
        return self.session.query(User).filter_by(email=email).first()
    def save(self, user):
        self.session.add(user); self.session.commit()

# Services use repositories — no raw SQL outside this layer
Live Preview
query result — Database-Backed App Architecture
Controller
Service
Repository
Database
✓ Best Practice: Repository pattern = all SQL in one layer. No scattered queries, swap databases without touching business logic.
Try it yourself

Why isolate database access in a repository?

Swap/test.
Testability, swappable databases, and consistent data access.
29

CAP & Consistency in Practice

24 min
What you'll learn
  • Apply CAP to real systems
  • Choose consistency models
  • Design for eventual consistency

In practice, systems choose a consistency model: strong (every read sees latest write) vs eventual (reads may be stale briefly but converge). Eventual consistency powers NoSQL and replicas; strong consistency is needed for banking. The design question: can your app tolerate brief staleness? If yes, eventual consistency unlocks massive scale; if no, you pay the coordination cost.

consistency.py
# Strong: every read after write sees the new value (banking)
# Eventual: reads may lag briefly, then converge (social feeds)
# Which does your app need?
Live Preview
query result — CAP & Consistency in Practice
Strong
vs
Eventual
🔎 Important: The consistency question is a business question: can users tolerate brief staleness? The answer shapes your entire architecture.
Try it yourself

Banking requires which consistency?

Balances.
Strong consistency — no stale balances allowed.
30

Capstone: Build a Scalable Blog Database

60 min
What you'll learn
  • Apply the full database skill set
  • Design schema, indexes, caching
  • Optimize and document

Your capstone: design a scalable blog database. Schema for users, posts, comments, tags (many-to-many). Add indexes for hot queries, write advanced queries (window functions for trending posts, full-text search), design a Redis caching layer, and document your scaling plan. This integrates everything — schema design, optimization, caching, and production thinking.

capstone.sql
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title TEXT, body TEXT,
    author_id INT REFERENCES users(id),
    created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_posts_created ON posts(created_at DESC);
-- Cache top posts in Redis
-- Full-text search on title/body
-- Replication for read scaling
Live Preview
query result — Capstone: Build a Scalable Blog Database
users / posts / comments / tags
indexes + caching + search
✓ Best Practice: This capstone = schema + indexes + caching + scaling — the complete production database skill set.
Try it yourself

Name three things a production blog database needs.

Scale.
Indexes, caching, full-text search, replication — all covered in this course.
You've completed all 30 intermediate lessons. Ready for advanced?

Continue to Databases Advanced for distributed systems, deep optimization, and data warehousing.

📱 Scan this QR code with your phone camera to instantly open this page.

Works on iOS, Android, and any modern device. No app installation required.

Account Verified!

Your email has been verified successfully.