Nextgen Blog
HomeLearnAIQuantumRoboticsSecuritySpaceAbout
Nextgen Blog

By Anubhav — full stack engineer, IIT Mandi CSE. Deep dives into AI, software engineering, and the technologies shaping the next generation of tech.

Navigate

  • Home
  • Learn
  • Blog
  • Archive
  • About

Topics

  • Artificial Intelligence
  • Quantum Computing
  • Robotics
  • Cybersecurity
  • Space Tech

Connect

Built with ❤️ & By - Anubhav

© 2026 Nextgen Blog by Anubhav. All rights reserved.

Privacy PolicyTerms of Service
Phase 02 · Scaling, Traffic & Data StoresChapter 12 of 33
11.
Phase 02Read time ~34 minDifficulty advanced

SQL Databases

In this chapter
  • 01What You Will Learn
  • 02What Is a SQL Database?
  • 03SQL Is Declarative
  • 04SQL Database Structure
  • 05Creating a Table
  • 06id
  • 07name
  • 08email
  • 09age
  • 10created_at
  • 11Common SQL Data Types
  • 12Integer
  • 13Decimal
  • 14Boolean
  • 15Text
  • 16Timestamp
  • 17Date
  • 18JSON
  • 19INSERT
  • 20Why Explicit Columns Are Better
  • 21SELECT
  • 22Why SELECT * Can Be a Problem
  • 23WHERE
  • 24Comparison Operators
  • 25AND
  • 26OR
  • 27NOT
  • 28IN
  • 29BETWEEN
  • 30LIKE
  • 31NULL
  • 32Checking NULL
  • 33SQL Three-Valued Logic
  • 34ORDER BY
  • 35LIMIT
  • 36OFFSET Pagination
  • 37OFFSET Pagination Problems
  • 38Cursor/Keyset Pagination
  • 39UPDATE
  • 40DELETE
  • 41Soft Delete
  • 42Aggregate Functions
  • 43COUNT
  • 44SUM
  • 45AVG
  • 46GROUP BY
  • 47HAVING
  • 48WHERE vs HAVING
  • 49JOINs
  • 50INNER JOIN
  • 51LEFT JOIN
  • 52INNER JOIN vs LEFT JOIN
  • 53JOIN Example: Order History
  • 54Table Aliases
  • 55Many-to-Many JOIN
  • 56Self JOIN
  • 57JOIN Performance
  • 58Subqueries
  • 59EXISTS
  • 60IN vs EXISTS
  • 61Common Table Expressions
  • 62Window Functions
  • 63Constraints
  • 64Foreign Key Constraints
  • 65Referential Actions
  • 66Transactions in SQL
  • 67Why Transactions Matter
  • 68Isolation Levels
  • 69READ COMMITTED
  • 70REPEATABLE READ
  • 71SERIALIZABLE
  • 72Isolation Trade-Off
  • 73Query Execution
  • 74Query Planner
  • 75Sequential Scan
  • 76Index Scan
  • 77EXPLAIN
  • 78EXPLAIN ANALYZE
  • 79Why Query Plans Matter
  • 80Indexes in SQL Databases
  • 81Unique Index
  • 82Composite Index
  • 83Leftmost Prefix Intuition
  • 84Covering Index
  • 85Index Costs
  • 86Indexing and Write-Heavy Systems
  • 87N+1 Query Problem
  • 88SQL Injection
  • 89ORM vs Raw SQL
  • 90ORM Example
  • 91Transactions and ORMs
  • 92SQL Performance Checklist
  • 93Step 1
  • 94Step 2
  • 95Step 3
  • 96Step 4
  • 97Step 5
  • 98Step 6
  • 99Step 7
  • 100Real-World Example — Product Listing
  • 101Real-World Example — User Orders
  • 102Real-World Example — Recent Events
  • 103SQL and System Design
  • 104Database Load Example
  • 105Connection Pool Calculation Intuition
  • 106Multiple Application Servers and Connection Pools
  • 107Database Overload Cascade
  • 108Production SQL Principles
  • 109Query intentionally
  • 110Parameterize input
  • 111Index based on access patterns
  • 112Measure
  • 113Keep transactions appropriate
  • 114Control concurrency
  • 115Plan for growth
  • 116Monitor
  • 117Common SQL Mistakes
  • 118Mistake 1: Forgetting WHERE
  • 119Mistake 2: Incorrect NULL comparison
  • 120Mistake 3: Blind SELECT *
  • 121Mistake 4: Assuming an index is always used
  • 122Mistake 5: Creating too many indexes
  • 123Mistake 6: N+1 queries
  • 124Mistake 7: Large OFFSET pagination
  • 125Mistake 8: String-concatenated SQL
  • 126Mistake 9: Huge transactions
  • 127Mistake 10: Assuming SQL behavior is identical across databases
  • 128Interview Questions
  • 129Beginner
  • 130Q1. What is SQL?
  • 131Q2. What is a table?
  • 132Q3. What is a primary key?
  • 133Q4. What is a foreign key?
  • 134Q5. What is a JOIN?
  • 135Intermediate
  • 136Q6. WHERE vs HAVING?
  • 137Q7. INNER JOIN vs LEFT JOIN?
  • 138Q8. What is an index?
  • 139Q9. Why can indexes hurt writes?
  • 140Q10. What is EXPLAIN?
  • 141Advanced Interview Questions
  • 142Q11. Why does a database sometimes ignore an index?
  • 143Q12. How would you optimize a slow query?
  • 144Q13. How would you paginate 100 million rows?
  • 145Q14. How do you prevent SQL injection?
  • 146Q15. What causes N+1 queries?
  • 147Practical Exercises
  • 148Exercise 1 — Users
  • 149Exercise 2 — Products
  • 150Exercise 3 — Orders
  • 151Mini Project — E-Commerce SQL Database
  • 152Requirements
  • 153Part 1 — Schema
  • 154Part 2 — Data
  • 155Part 3 — Queries
  • 156Part 4 — Performance
  • 157Part 5 — Transactions
  • 158System Design Connection
  • 159Chapter Summary
  • 160Master Checklist
  • 161What Comes Next?
  • 162Chapter 11 — NoSQL Databases
In this chapter (162 sections)
01What You Will Learn02What Is a SQL Database?03SQL Is Declarative04SQL Database Structure05Creating a Table06id07name08email09age10created_at11Common SQL Data Types12Integer13Decimal14Boolean15Text16Timestamp17Date18JSON19INSERT20Why Explicit Columns Are Better21SELECT22Why SELECT * Can Be a Problem23WHERE24Comparison Operators25AND26OR27NOT28IN29BETWEEN30LIKE31NULL32Checking NULL33SQL Three-Valued Logic34ORDER BY35LIMIT36OFFSET Pagination37OFFSET Pagination Problems38Cursor/Keyset Pagination39UPDATE40DELETE41Soft Delete42Aggregate Functions43COUNT44SUM45AVG46GROUP BY47HAVING48WHERE vs HAVING49JOINs50INNER JOIN51LEFT JOIN52INNER JOIN vs LEFT JOIN53JOIN Example: Order History54Table Aliases55Many-to-Many JOIN56Self JOIN57JOIN Performance58Subqueries59EXISTS60IN vs EXISTS61Common Table Expressions62Window Functions63Constraints64Foreign Key Constraints65Referential Actions66Transactions in SQL67Why Transactions Matter68Isolation Levels69READ COMMITTED70REPEATABLE READ71SERIALIZABLE72Isolation Trade-Off73Query Execution74Query Planner75Sequential Scan76Index Scan77EXPLAIN78EXPLAIN ANALYZE79Why Query Plans Matter80Indexes in SQL Databases81Unique Index82Composite Index83Leftmost Prefix Intuition84Covering Index85Index Costs86Indexing and Write-Heavy Systems87N+1 Query Problem88SQL Injection89ORM vs Raw SQL90ORM Example91Transactions and ORMs92SQL Performance Checklist93Step 194Step 295Step 396Step 497Step 598Step 699Step 7100Real-World Example — Product Listing101Real-World Example — User Orders102Real-World Example — Recent Events103SQL and System Design104Database Load Example105Connection Pool Calculation Intuition106Multiple Application Servers and Connection Pools107Database Overload Cascade108Production SQL Principles109Query intentionally110Parameterize input111Index based on access patterns112Measure113Keep transactions appropriate114Control concurrency115Plan for growth116Monitor117Common SQL Mistakes118Mistake 1: Forgetting WHERE119Mistake 2: Incorrect NULL comparison120Mistake 3: Blind SELECT *121Mistake 4: Assuming an index is always used122Mistake 5: Creating too many indexes123Mistake 6: N+1 queries124Mistake 7: Large OFFSET pagination125Mistake 8: String-concatenated SQL126Mistake 9: Huge transactions127Mistake 10: Assuming SQL behavior is identical across databases128Interview Questions129Beginner130Q1. What is SQL?131Q2. What is a table?132Q3. What is a primary key?133Q4. What is a foreign key?134Q5. What is a JOIN?135Intermediate136Q6. WHERE vs HAVING?137Q7. INNER JOIN vs LEFT JOIN?138Q8. What is an index?139Q9. Why can indexes hurt writes?140Q10. What is EXPLAIN?141Advanced Interview Questions142Q11. Why does a database sometimes ignore an index?143Q12. How would you optimize a slow query?144Q13. How would you paginate 100 million rows?145Q14. How do you prevent SQL injection?146Q15. What causes N+1 queries?147Practical Exercises148Exercise 1 — Users149Exercise 2 — Products150Exercise 3 — Orders151Mini Project — E-Commerce SQL Database152Requirements153Part 1 — Schema154Part 2 — Data155Part 3 — Queries156Part 4 — Performance157Part 5 — Transactions158System Design Connection159Chapter Summary160Master Checklist161What Comes Next?162Chapter 11 — NoSQL Databases

Course: System Design — Zero to Hero
Level: Beginner → Advanced
Phase: Database Fundamentals
Status: Complete


1. What You Will Learn#

In Chapter 09, we learned what databases are, how relational data is modeled, why indexes and transactions matter, and how SQL databases differ from other database families.

Now we will go deeper into SQL databases.

You will learn:

  • What a SQL database is
  • Database schemas
  • Tables and columns
  • SQL data types
  • CREATE TABLE
  • INSERT
  • SELECT
  • WHERE
  • Comparison operators
  • AND, OR, NOT
  • IN, BETWEEN, LIKE
  • NULL
  • ORDER BY
  • LIMIT and pagination
  • UPDATE
  • DELETE
  • Aggregate functions
  • GROUP BY
  • HAVING
  • JOIN
  • Inner joins
  • Left joins
  • Self joins
  • Many-to-many queries
  • Subqueries
  • Common table expressions
  • Transactions
  • COMMIT and ROLLBACK
  • Isolation levels
  • Query execution
  • Query plans
  • EXPLAIN
  • Indexes
  • Composite indexes
  • Covering indexes
  • Constraints
  • Foreign keys
  • SQL performance
  • Common SQL mistakes
  • Production SQL patterns
  • System-design implications
  • Interview questions
  • Practical exercises
  • A mini project

The examples will use PostgreSQL-style SQL because it is widely used and gives us a strong foundation for system design.

Most concepts transfer to other relational databases, although exact syntax and behavior can differ.


2. What Is a SQL Database?#

A SQL database is a relational database that provides a structured way to store and query data, commonly using SQL — Structured Query Language.

Examples include:

  • PostgreSQL
  • MySQL
  • MariaDB
  • Microsoft SQL Server
  • Oracle Database

A simplified architecture is:

Application
     |
     v
Database Driver / ORM
     |
     v
SQL Database
     |
     +---- Query Parser
     +---- Query Planner
     +---- Query Executor
     +---- Buffer / Memory
     +---- Storage
     +---- Transaction System

The application sends a query.

The database:

receives query
     ↓
parses query
     ↓
plans execution
     ↓
executes operations
     ↓
returns result

3. SQL Is Declarative#

One of the most important ideas is that SQL is primarily declarative.

Consider:

SELECT name
FROM users
WHERE age >= 18;

You tell the database:

Give me names of users whose age is at least 18.

You generally do not specify:

Open file
Read row 1
Check age
Read row 2
Check age
...

The database determines an execution strategy.

This separation is powerful:

Application
    |
    | What data do I need?
    v
SQL
    |
    v
Database optimizer
    |
    | How should I retrieve it?
    v
Execution plan

4. SQL Database Structure#

A typical relational database can be visualized as:

Database
 |
 +-- Schema
      |
      +-- users
      +-- products
      +-- orders
      +-- payments

A table contains:

Columns
   +
Rows

Example:

users
 
id | name    | email
---+---------+--------------------
1  | Anubhav | anubhav@example.com
2  | Rahul   | rahul@example.com
3  | Priya   | priya@example.com

5. Creating a Table#

Example:

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    age INTEGER,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Let's understand it.

id#

id BIGSERIAL PRIMARY KEY

Creates an identifier.

name#

name VARCHAR(100) NOT NULL

A string with a maximum length of 100 characters.

email#

email VARCHAR(255) NOT NULL UNIQUE

The email must:

  • exist
  • be unique

age#

age INTEGER

An integer.

created_at#

created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP

Stores creation time.


6. Common SQL Data Types#

Common types include:

Integer#

INTEGER
BIGINT

Useful for counts and identifiers.


Decimal#

NUMERIC(12,2)

Useful for exact decimal values such as monetary amounts.

For financial values, exact decimal/numeric types are generally preferred over floating-point types.


Boolean#

BOOLEAN

Example:

true
false

Text#

TEXT
VARCHAR(255)

Timestamp#

TIMESTAMP
TIMESTAMPTZ

The exact choice depends on the application's time semantics and database conventions.


Date#

DATE

JSON#

PostgreSQL supports:

JSON
JSONB

JSONB is commonly useful when you need JSON-shaped data with database-side operations and indexing.


7. INSERT#

To insert data:

INSERT INTO users (name, email, age)
VALUES ('Anubhav', 'anubhav@example.com', 28);

Multiple rows:

INSERT INTO users (name, email, age)
VALUES
    ('Anubhav', 'anubhav@example.com', 28),
    ('Rahul', 'rahul@example.com', 30),
    ('Priya', 'priya@example.com', 26);

8. Why Explicit Columns Are Better#

Avoid relying on:

INSERT INTO users
VALUES (...);

Prefer:

INSERT INTO users (name, email, age)
VALUES (...);

Why?

Because the table schema may change.

Explicit columns make the query:

  • clearer
  • safer
  • easier to maintain

9. SELECT#

The most fundamental SQL operation is:

SELECT

Example:

SELECT *
FROM users;

This returns all columns.

But production applications often should select only what they need.

Prefer:

SELECT id, name, email
FROM users;

instead of:

SELECT *
FROM users;

when the application does not need every field.


10. Why SELECT * Can Be a Problem#

Suppose a table contains:

id
name
email
profile_photo
large_metadata
created_at
updated_at

If you need only:

id
name

then:

SELECT *

may retrieve unnecessary data.

Potential consequences:

  • more database work
  • more network transfer
  • more application memory
  • more serialization/deserialization
  • less efficient APIs

It is not that SELECT * is always forbidden.

The important principle is:

Retrieve only what the application actually needs.


11. WHERE#

WHERE filters rows.

SELECT id, name
FROM users
WHERE age >= 18;

Conceptually:

All users
   |
   v
Filter age >= 18
   |
   v
Matching users

12. Comparison Operators#

Common operators:

=
<>
!=
>
<
>=
<=

Examples:

WHERE age = 25
WHERE age > 18
WHERE age <= 30

13. AND#

SELECT *
FROM users
WHERE age >= 18
AND age <= 30;

Both conditions must be true.


14. OR#

SELECT *
FROM users
WHERE city = 'Delhi'
OR city = 'Mumbai';

Either condition may be true.

Use parentheses when combining complicated conditions.

WHERE
    (city = 'Delhi' OR city = 'Mumbai')
    AND age >= 18;

15. NOT#

SELECT *
FROM users
WHERE NOT is_blocked;

This selects rows where is_blocked is not true.

Be careful with NULL, because SQL's three-valued logic means:

TRUE
FALSE
UNKNOWN

not simply true/false.


16. IN#

Instead of:

WHERE city = 'Delhi'
OR city = 'Mumbai'
OR city = 'Bangalore'

you can write:

WHERE city IN ('Delhi', 'Mumbai', 'Bangalore');

This is usually easier to read.


17. BETWEEN#

Example:

SELECT *
FROM products
WHERE price BETWEEN 100 AND 500;

BETWEEN is inclusive at both boundaries in typical SQL semantics:

100 <= price <= 500

Always understand the exact semantics when working with dates and timestamps.


18. LIKE#

Pattern matching:

SELECT *
FROM users
WHERE name LIKE 'Anu%';

This can match values beginning with:

Anu

For example:

Anubhav
Anurag
Anuj

A leading wildcard:

WHERE name LIKE '%hav';

can have different performance characteristics and may prevent efficient use of an ordinary B-tree index depending on the database and pattern.


19. NULL#

NULL means:

Missing/unknown/not present value.

It is not:

0

and not:

''

and not necessarily:

false

20. Checking NULL#

Incorrect:

WHERE email = NULL;

Correct:

WHERE email IS NULL;

And:

WHERE email IS NOT NULL;

This is a very common interview and practical mistake.


21. SQL Three-Valued Logic#

SQL expressions can evaluate to:

TRUE
FALSE
UNKNOWN

For example:

NULL = 5

does not evaluate to ordinary FALSE; it produces UNKNOWN.

This is why NULL requires special handling.


22. ORDER BY#

Sort results:

SELECT id, name, created_at
FROM users
ORDER BY created_at DESC;

Ascending:

ORDER BY created_at ASC;

Descending:

ORDER BY created_at DESC;

23. LIMIT#

Return only a limited number of rows:

SELECT *
FROM users
ORDER BY created_at DESC
LIMIT 20;

This is useful for:

  • recent items
  • top results
  • pagination
  • APIs

But LIMIT alone does not guarantee stable pagination.


24. OFFSET Pagination#

A basic pagination query:

SELECT *
FROM products
ORDER BY id
LIMIT 20
OFFSET 40;

This means:

Skip 40
Return next 20

Page 1:

LIMIT 20 OFFSET 0

Page 2:

LIMIT 20 OFFSET 20

Page 3:

LIMIT 20 OFFSET 40

25. OFFSET Pagination Problems#

For very large offsets:

OFFSET 1000000

the database may need to process/skip many rows.

This can become expensive.

Another problem is changing data.

If new rows are inserted between requests, users may see:

  • duplicates
  • missing rows

depending on the ordering and transaction behavior.

For large datasets, another approach is often better.


26. Cursor/Keyset Pagination#

Suppose we order by:

ORDER BY id

Instead of:

OFFSET 1000000

we can use the last seen ID.

Example:

SELECT *
FROM products
WHERE id > 1000000
ORDER BY id
LIMIT 20;

This is often called keyset pagination or cursor-based pagination.

Conceptually:

Page 1
IDs 1-20
      |
      v
last_id = 20
 
Page 2
WHERE id > 20

This can scale much better for suitable ordered access patterns.


27. UPDATE#

Update data:

UPDATE users
SET name = 'Anubhav Chaudhary'
WHERE id = 1;

Important:

Always think carefully about the WHERE clause.

This:

UPDATE users
SET name = 'Unknown';

may update every row.


28. DELETE#

Example:

DELETE FROM users
WHERE id = 1;

Again, without a suitable WHERE:

DELETE FROM users;

can delete every row.

Production systems often use safeguards around destructive operations.


29. Soft Delete#

Instead of physically deleting a row:

DELETE FROM users
WHERE id = 1;

some applications use:

deleted_at

Example:

UPDATE users
SET deleted_at = CURRENT_TIMESTAMP
WHERE id = 1;

Then normal queries use:

WHERE deleted_at IS NULL

Advantages can include:

  • recovery
  • auditability
  • historical references

Trade-offs include:

  • larger tables
  • more complicated queries
  • indexing considerations
  • retention requirements

Soft delete should be an explicit design decision, not a universal rule.


30. Aggregate Functions#

SQL provides aggregate functions.

Common examples:

COUNT()
SUM()
AVG()
MIN()
MAX()

Example:

SELECT COUNT(*)
FROM users;

31. COUNT#

Count users:

SELECT COUNT(*)
FROM users;

Count users older than 18:

SELECT COUNT(*)
FROM users
WHERE age >= 18;

32. SUM#

Suppose:

orders
--------
amount

Then:

SELECT SUM(amount)
FROM orders;

can calculate the total.


33. AVG#

SELECT AVG(price)
FROM products;

Calculates the average price according to SQL's numeric semantics.


34. GROUP BY#

Suppose we want users grouped by city:

SELECT city, COUNT(*)
FROM users
GROUP BY city;

Result:

city       count
---------- -----
Delhi      1000
Mumbai     800
Bangalore  1200

Conceptually:

Rows
  |
  v
Group by city
  |
  v
Aggregate each group

35. HAVING#

WHERE filters rows before grouping.

HAVING filters groups after aggregation.

Example:

SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 1000;

Meaning:

Show cities whose user count is greater than 1,000.


36. WHERE vs HAVING#

Remember:

WHERE
  -> filters rows
 
GROUP BY
  -> creates groups
 
HAVING
  -> filters groups

Example:

SELECT city, COUNT(*)
FROM users
WHERE age >= 18
GROUP BY city
HAVING COUNT(*) > 100;

Logical flow:

FROM
 ↓
WHERE
 ↓
GROUP BY
 ↓
HAVING
 ↓
SELECT
 ↓
ORDER BY
 ↓
LIMIT

This is a useful conceptual model.


37. JOINs#

JOINs are one of the most important SQL concepts.

Suppose:

users
-----
id
name

and:

orders
------
id
user_id
amount

We want:

User name + order amount

We can join them.


38. INNER JOIN#

SELECT
    users.name,
    orders.amount
FROM users
INNER JOIN orders
    ON users.id = orders.user_id;

An inner join returns matching rows from both sides.

Conceptually:

users          orders
  |               |
  +------JOIN-----+
          |
          v
      matching data

39. LEFT JOIN#

SELECT
    users.name,
    orders.amount
FROM users
LEFT JOIN orders
    ON users.id = orders.user_id;

A left join keeps rows from the left table even when there is no matching row on the right.

Example:

Users:
 
Anubhav
Rahul
Priya
 
Orders:
 
Anubhav -> ₹500
Priya   -> ₹800

A left join can still return Rahul with NULL order information.


40. INNER JOIN vs LEFT JOIN#

JOIN Meaning
INNER JOIN Return matching rows
LEFT JOIN Keep all left-side rows
RIGHT JOIN Keep all right-side rows
FULL OUTER JOIN Keep rows from both sides where supported

In practice, INNER JOIN and LEFT JOIN are especially common.


41. JOIN Example: Order History#

Find all orders for user 123:

SELECT
    o.id,
    o.amount,
    o.created_at
FROM orders o
WHERE o.user_id = 123
ORDER BY o.created_at DESC;

Now include user information:

SELECT
    u.name,
    o.id,
    o.amount,
    o.created_at
FROM users u
JOIN orders o
    ON u.id = o.user_id
WHERE u.id = 123
ORDER BY o.created_at DESC;

42. Table Aliases#

Instead of:

users.name
orders.amount
orders.created_at

we can use:

u.name
o.amount
o.created_at

Example:

SELECT
    u.name,
    o.amount
FROM users AS u
JOIN orders AS o
    ON u.id = o.user_id;

Aliases make complex SQL much easier to read.


43. Many-to-Many JOIN#

Suppose:

students
courses
student_courses

To find courses for student 10:

SELECT c.id, c.name
FROM courses c
JOIN student_courses sc
    ON c.id = sc.course_id
WHERE sc.student_id = 10;

The junction table connects the two entities.


44. Self JOIN#

A table can reference itself.

Example:

employees
 
id
name
manager_id

To find 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;

This is a self join.


45. JOIN Performance#

JOINs are powerful, but at scale we should understand:

  • join columns
  • indexes
  • cardinality
  • result size
  • filtering
  • query plan

For example:

orders.user_id

should often have an appropriate access path if queries frequently join/filter by it.

A join between huge tables can be expensive if the query is poorly designed.


46. Subqueries#

A subquery is a query inside another query.

Example:

SELECT *
FROM products
WHERE price > (
    SELECT AVG(price)
    FROM products
);

Meaning:

Find products whose price is above the average.


47. EXISTS#

Suppose we want users who have at least one order:

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.user_id = u.id
);

EXISTS is often useful when we care about whether a matching row exists rather than retrieving the matching rows themselves.


48. IN vs EXISTS#

Both can express certain membership queries.

Example:

WHERE user_id IN (...)

or:

WHERE EXISTS (...)

The best choice depends on:

  • query shape
  • database optimizer
  • indexes
  • data distribution

Do not memorize:

EXISTS is always faster.

That is not a reliable rule.

Use EXPLAIN and measurements.


49. Common Table Expressions#

A Common Table Expression is written with:

WITH

Example:

WITH high_value_orders AS (
    SELECT *
    FROM orders
    WHERE amount > 10000
)
SELECT *
FROM high_value_orders;

CTEs can make complex queries easier to structure.


50. Window Functions#

Window functions are extremely useful for analytics.

Example:

SELECT
    user_id,
    amount,
    ROW_NUMBER() OVER (
        PARTITION BY user_id
        ORDER BY created_at DESC
    ) AS order_number
FROM orders;

Unlike GROUP BY, window functions can calculate information across related rows while retaining individual rows.

Common window functions:

ROW_NUMBER()
RANK()
DENSE_RANK()
LAG()
LEAD()
SUM() OVER (...)
AVG() OVER (...)

These become especially useful for reporting and analytical workloads.


51. Constraints#

SQL databases provide mechanisms for protecting data integrity.

Example:

CREATE TABLE products (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    price NUMERIC(12,2) CHECK (price >= 0),
    sku TEXT UNIQUE
);

Here:

PRIMARY KEY
NOT NULL
CHECK
UNIQUE

all enforce rules.


52. Foreign Key Constraints#

Example:

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL,
    amount NUMERIC(12,2) NOT NULL,
 
    CONSTRAINT fk_orders_user
        FOREIGN KEY (user_id)
        REFERENCES users(id)
);

This prevents an order from referencing a nonexistent user, subject to the database's constraint behavior.


53. Referential Actions#

Foreign keys can define behavior when referenced rows are changed.

Examples include:

CASCADE
RESTRICT
SET NULL

Example:

FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE

This can cause related rows to be deleted automatically.

Be careful.

Cascading deletes can be useful but can also produce unexpectedly large operations.


54. Transactions in SQL#

A basic transaction:

BEGIN;
 
UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;
 
UPDATE accounts
SET balance = balance + 1000
WHERE id = 2;
 
COMMIT;

If something fails:

ROLLBACK;

The transaction defines a boundary around the operations.


55. Why Transactions Matter#

Consider an order workflow:

Create order
Reduce inventory
Create payment record

If these operations have a strict requirement to succeed together, we need to think carefully about transaction boundaries.

However, a transaction should not automatically span an entire distributed workflow.

For example:

Order Service
      |
      v
Payment Service
      |
      v
Notification Service

A single database transaction may not safely or practically cover all of these independent services.

Later chapters will introduce distributed transactions and event-driven patterns.


56. Isolation Levels#

Common SQL isolation levels:

READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

They represent different trade-offs between:

Concurrency
      vs
Isolation guarantees

Exact semantics differ between database engines.

For example, PostgreSQL's implementation and defaults should be understood independently from another database's behavior.


57. READ COMMITTED#

A simplified mental model:

A transaction sees data committed before each statement begins, plus its own changes.

This can still allow certain values to change between statements within the same transaction.


58. REPEATABLE READ#

Provides stronger repeatability semantics for reads within a transaction.

The exact behavior depends on the database implementation.

The key idea:

A transaction gets a more stable view of data than under weaker isolation.


59. SERIALIZABLE#

Serializable isolation aims to provide behavior equivalent to transactions executing serially, although the database may implement this using concurrency-control techniques rather than literally running one transaction at a time.

This can provide stronger correctness guarantees but may reduce concurrency or cause transactions to fail/retry.


60. Isolation Trade-Off#

Think:

Higher isolation
       |
       +--> stronger guarantees
       |
       +--> potentially more contention/retries
       |
       +--> potentially lower concurrency

The right isolation level depends on the application's correctness requirements.


61. Query Execution#

When you send:

SELECT *
FROM users
WHERE email = 'anubhav@example.com';

the database does not simply "run the text."

A simplified pipeline is:

SQL text
   |
   v
Parser
   |
   v
Parsed representation
   |
   v
Query planner / optimizer
   |
   v
Execution plan
   |
   v
Executor
   |
   v
Result

62. Query Planner#

The optimizer considers possible execution strategies.

For example:

Option A:
Sequential scan
 
Option B:
Index scan
 
Option C:
Bitmap/index-based strategy

The optimizer estimates costs and chooses a plan.

This is why:

Having an index does not guarantee the database will use it.


63. Sequential Scan#

A sequential scan reads rows from the table in sequence.

Conceptually:

Row 1
Row 2
Row 3
...
Row N

This may be appropriate when:

  • the table is small
  • a large percentage of rows are needed
  • an index would not be cheaper
  • the query requires a broad scan

A sequential scan is not automatically bad.


64. Index Scan#

An index scan uses an index to locate relevant rows.

Conceptually:

Query
  |
  v
Index
  |
  v
Relevant row locations
  |
  v
Table rows

This can be efficient for selective queries.


65. EXPLAIN#

Use:

EXPLAIN
SELECT *
FROM users
WHERE email = 'anubhav@example.com';

This asks the database to show the planned execution strategy.


66. EXPLAIN ANALYZE#

In PostgreSQL:

EXPLAIN ANALYZE
SELECT *
FROM users
WHERE email = 'anubhav@example.com';

This executes the query and provides actual execution information.

It can help compare:

Estimated rows
vs
Actual rows
 
Estimated cost
vs
Actual execution behavior

Use caution with EXPLAIN ANALYZE on queries that modify data, because it executes them unless wrapped appropriately or otherwise handled.


67. Why Query Plans Matter#

Suppose a query is slow.

Do not immediately say:

Add an index.

First investigate.

Possible causes:

Bad index
Missing index
Wrong index order
Large scan
Poor join strategy
Bad cardinality estimate
Outdated statistics
Too many rows returned
Lock contention
Disk I/O
CPU pressure

EXPLAIN helps turn guesswork into evidence.


68. Indexes in SQL Databases#

A common index:

CREATE INDEX idx_users_email
ON users(email);

Now queries filtering by email may have an efficient access path.


69. Unique Index#

A uniqueness requirement can be represented using:

CREATE UNIQUE INDEX idx_users_email_unique
ON users(email);

But when the requirement is a data-integrity constraint, a UNIQUE constraint is often the clearer schema-level expression:

ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email);

The database may implement the uniqueness enforcement using an index.


70. Composite Index#

Suppose the application frequently queries:

SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;

A candidate index could be:

CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);

This aligns the index with:

filter -> user_id
sort   -> created_at

The exact usefulness should be validated with the query plan.


71. Leftmost Prefix Intuition#

For an index:

(user_id, created_at)

the first indexed column is particularly important.

Queries involving:

user_id

or:

user_id + created_at

may benefit naturally.

A query filtering only on:

created_at

may not get the same benefit from this index.

This is a useful mental model, although exact optimizer behavior depends on the database engine and query.


72. Covering Index#

Suppose:

SELECT user_id, created_at
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC;

If the index contains all information required for the query, the database may be able to answer more of the query from the index itself.

This is broadly called a covering-index optimization.

PostgreSQL also provides mechanisms such as included columns:

CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC)
INCLUDE (amount, status);

This is an advanced optimization and should be introduced only when measurements justify it.


73. Index Costs#

Every index has costs.

Index
 |
 +--> storage
 +--> memory/cache usage
 +--> insert overhead
 +--> update overhead
 +--> delete overhead
 +--> maintenance

Suppose:

Table has 15 indexes

Every insert may need to maintain many structures.

Therefore:

Index based on actual access patterns.


74. Indexing and Write-Heavy Systems#

Imagine:

100,000 writes/sec

and:

20 indexes

Index maintenance can become significant.

Sometimes removing an unnecessary index can improve write performance.

This is why database design is a trade-off:

Read performance
       vs
Write cost

75. N+1 Query Problem#

A common application mistake:

1 query -> fetch users
 
Then:
1 query per user -> fetch orders

For 1,000 users:

1 + 1,000 = 1,001 queries

This is the N+1 problem.

A join or batch query may reduce this substantially.

Example:

SELECT
    u.id,
    u.name,
    o.id AS order_id,
    o.amount
FROM users u
LEFT JOIN orders o
    ON o.user_id = u.id;

The correct solution depends on result size and application requirements, but blindly issuing thousands of database round trips is often a problem.


76. SQL Injection#

Never construct SQL by concatenating untrusted user input.

Dangerous pattern:

const query =
  "SELECT * FROM users WHERE email = '" +
  email +
  "'";

If email is attacker-controlled, the query may be manipulated.

Use parameterized queries.

Example conceptually:

SELECT *
FROM users
WHERE email = $1;

with:

$1 = user-provided email

The database driver handles parameter binding.


77. ORM vs Raw SQL#

Applications often use ORMs such as:

Prisma
Hibernate
Entity Framework
Sequelize
TypeORM

ORMs can improve:

  • developer productivity
  • type safety
  • schema modeling
  • maintainability

But developers still need SQL knowledge.

An ORM can generate inefficient queries.

Therefore:

An ORM does not remove the need to understand databases.


78. ORM Example#

Conceptually:

const users = await prisma.user.findMany({
  where: {
    age: {
      gte: 18
    }
  }
});

The ORM may generate SQL similar to:

SELECT ...
FROM users
WHERE age >= 18;

When performance becomes a problem, you need to understand what SQL is actually being executed.


79. Transactions and ORMs#

Modern ORMs usually support transactions.

Conceptually:

await db.transaction(async (tx) => {
    // operation 1
    // operation 2
    // operation 3
});

The important system-design question remains:

Which operations belong in the same transaction?

Not:

Does the ORM have a transaction API?


80. SQL Performance Checklist#

When a SQL query is slow:

Step 1#

Reproduce the query.

Step 2#

Measure latency.

Step 3#

Run:

EXPLAIN

or the database's equivalent.

Step 4#

Check:

Scan type
Rows estimated
Rows actual
Join strategy
Sort
Filter
Index usage

Step 5#

Check infrastructure:

CPU
Memory
Disk I/O
Connections
Locks

Step 6#

Optimize.

Potential actions:

Add/change index
Rewrite query
Reduce returned columns
Reduce result set
Change pagination
Batch requests
Fix N+1
Denormalize where justified

Step 7#

Measure again.


81. Real-World Example — Product Listing#

Suppose an e-commerce application needs:

GET /products?category=phones&limit=20

A query might be:

SELECT id, name, price
FROM products
WHERE category_id = 10
  AND is_active = true
ORDER BY created_at DESC
LIMIT 20;

Potential index:

(category_id, is_active, created_at)

But don't blindly create it.

Test the actual workload and execution plan.


82. Real-World Example — User Orders#

API:

GET /users/123/orders

Query:

SELECT id, status, amount, created_at
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;

A candidate index:

(user_id, created_at DESC)

This is a classic system-design access pattern.


83. Real-World Example — Recent Events#

Suppose:

events
------
id
user_id
event_type
created_at
payload

Query:

SELECT id, event_type, created_at
FROM events
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 100;

The index should be designed around:

user_id
created_at

rather than indexing every column.


84. SQL and System Design#

SQL knowledge becomes system-design knowledge when you connect queries to system behavior.

For example:

API endpoint
    |
    v
SQL query
    |
    v
Index
    |
    v
Database CPU / I/O
    |
    v
Latency

A bad query can become a system-wide bottleneck.


85. Database Load Example#

Suppose:

10,000 requests/sec

and every request executes:

3 SQL queries

Then:

10,000 × 3
=
30,000 SQL queries/sec

If each request executes 20 queries:

10,000 × 20
=
200,000 SQL queries/sec

This is why query count matters.

Reducing unnecessary round trips can be more important than micro-optimizing individual lines of application code.


86. Connection Pool Calculation Intuition#

Suppose:

Request rate = 1,000 requests/sec
Average database time per request = 20 ms

A rough concurrency relationship comes from Little's Law:

Concurrency ≈ Throughput × Latency

So:

1,000 × 0.020
=
20 concurrent database operations

This does not mean:

Set the connection pool exactly to 20.

Real systems need to account for:

  • query distributions
  • bursts
  • multiple query types
  • application instances
  • database capacity
  • queueing
  • headroom

But it demonstrates why latency and concurrency are connected.


87. Multiple Application Servers and Connection Pools#

Suppose:

20 application servers

and each has:

pool size = 50

Potential maximum connections:

20 × 50
=
1,000 connections

This is a critical system-design calculation.

Developers sometimes increase each application's pool independently without realizing that the total connection count grows with the number of application instances.


88. Database Overload Cascade#

Consider:

Traffic increases
      |
      v
Application servers scale
      |
      v
More DB connections
      |
      v
Database CPU increases
      |
      v
Queries become slower
      |
      v
Connections remain occupied longer
      |
      v
Connection pool fills
      |
      v
Requests time out
      |
      v
Clients retry
      |
      v
Traffic increases further

This is a classic cascading failure.

Possible defenses:

Connection limits
Timeouts
Backpressure
Rate limiting
Caching
Query optimization
Load shedding
Circuit breakers
Retry budgets

89. Production SQL Principles#

Remember:

1. Query intentionally#

Know what data you need.

2. Parameterize input#

Prevent SQL injection.

3. Index based on access patterns#

Not assumptions.

4. Measure#

Use query plans and metrics.

5. Keep transactions appropriate#

Neither too broad nor unnecessarily fragmented.

6. Control concurrency#

More database concurrency is not automatically better.

7. Plan for growth#

A query that works on:

10,000 rows

may fail at:

1 billion rows

8. Monitor#

Track:

  • query latency
  • slow queries
  • errors
  • locks
  • connection utilization
  • CPU
  • memory
  • I/O

90. Common SQL Mistakes#

Mistake 1: Forgetting WHERE#

UPDATE users
SET status = 'inactive';

Could update every user.


Mistake 2: Incorrect NULL comparison#

Wrong:

WHERE email = NULL

Correct:

WHERE email IS NULL

Mistake 3: Blind SELECT *#

Can retrieve unnecessary data.


Mistake 4: Assuming an index is always used#

The optimizer chooses a plan.


Mistake 5: Creating too many indexes#

Indexes have write and storage costs.


Mistake 6: N+1 queries#

Can generate huge numbers of database round trips.


Mistake 7: Large OFFSET pagination#

Can become expensive at scale.


Mistake 8: String-concatenated SQL#

Creates SQL injection risk.


Mistake 9: Huge transactions#

Long transactions can increase:

  • locks
  • resource usage
  • contention
  • failure impact

Mistake 10: Assuming SQL behavior is identical across databases#

PostgreSQL, MySQL, SQL Server, and Oracle can differ in:

  • syntax
  • optimizer behavior
  • isolation implementation
  • data types
  • indexing
  • locking
  • defaults

Always verify database-specific behavior.


91. Interview Questions#

Beginner#

Q1. What is SQL?#

A language used to define, query, manipulate, and manage data in relational database systems.

Q2. What is a table?#

A structured collection of rows organized into columns.

Q3. What is a primary key?#

A key that uniquely identifies a row.

Q4. What is a foreign key?#

A key that references a key in another table and can enforce referential integrity.

Q5. What is a JOIN?#

An operation that combines rows from multiple tables based on a relationship or condition.


Intermediate#

Q6. WHERE vs HAVING?#

WHERE  -> filters rows
HAVING -> filters groups after aggregation

Q7. INNER JOIN vs LEFT JOIN?#

INNER JOIN -> matching rows
LEFT JOIN  -> all left rows + matching right rows

Q8. What is an index?#

An auxiliary data structure used to make certain access patterns more efficient.

Q9. Why can indexes hurt writes?#

Because inserts, updates, and deletes may need to maintain the index structures.

Q10. What is EXPLAIN?#

A mechanism for inspecting the database's planned query execution strategy.


92. Advanced Interview Questions#

Q11. Why does a database sometimes ignore an index?#

Possible reasons:

  • query returns a large percentage of rows
  • sequential scan is estimated to be cheaper
  • index is poorly aligned with query
  • statistics/cardinality estimates
  • expression/function prevents normal index use
  • table is small

Q12. How would you optimize a slow query?#

A strong answer:

Measure
  ↓
EXPLAIN / EXPLAIN ANALYZE
  ↓
Inspect plan
  ↓
Check indexes
  ↓
Check row estimates
  ↓
Check joins/sorts/scans
  ↓
Check locks/I/O/CPU
  ↓
Rewrite or index
  ↓
Measure again

Q13. How would you paginate 100 million rows?#

For suitable ordered access:

Avoid huge OFFSET
Use cursor/keyset pagination
Use indexed ordering

Example:

SELECT *
FROM orders
WHERE id > $1
ORDER BY id
LIMIT 50;

Q14. How do you prevent SQL injection?#

Use:

Parameterized queries
Prepared statements
Safe ORM query APIs

Never rely on manually escaping strings as the primary defense.


Q15. What causes N+1 queries?#

Usually an application fetches a list and then performs another database query separately for each item.

Solutions can include:

  • joins
  • batching
  • eager loading
  • carefully designed data access layers

93. Practical Exercises#

Exercise 1 — Users#

Create:

users

with:

id
name
email
age
city
created_at

Write SQL to:

  1. Insert five users.
  2. Find all users.
  3. Find users older than 25.
  4. Find users from Delhi.
  5. Find users from Delhi or Mumbai.
  6. Sort users by creation time.
  7. Return the newest 10 users.
  8. Update one user's city.
  9. Delete one user.

Exercise 2 — Products#

Create:

products

with:

id
name
category
price
stock
created_at

Write queries to:

  1. Find products above ₹1,000.
  2. Find products between ₹500 and ₹2,000.
  3. Find products with stock greater than zero.
  4. Find the five most expensive products.
  5. Count products by category.
  6. Find categories containing more than 100 products.

Exercise 3 — Orders#

Create:

orders

with:

id
user_id
amount
status
created_at

Write queries to:

  1. Find all orders for one user.
  2. Find recent orders.
  3. Calculate total order value.
  4. Calculate average order value.
  5. Count orders by status.
  6. Find users whose order count exceeds a threshold.

94. Mini Project — E-Commerce SQL Database#

Design these tables:

users
products
orders
order_items
payments

Requirements#

A user can:

Create an account
Browse products
Place orders
Have multiple order items
Make payments
View order history

Part 1 — Schema#

Define:

Primary keys
Foreign keys
Constraints
Data types

Part 2 — Data#

Insert:

10 users
20 products
30 orders

Part 3 — Queries#

Implement:

User by email
Product by ID
Products by category
User's orders
Order details
Order total
Recent orders
Top-selling products

Part 4 — Performance#

Identify indexes for:

users.email
orders.user_id
orders.created_at
order_items.order_id
order_items.product_id

Do not assume every candidate must be indexed. Justify each index based on an access pattern.

Part 5 — Transactions#

Design a transaction for:

Create order
Create order items
Reduce inventory
Create payment record

Then discuss which parts should remain inside the transaction and which could be asynchronous in a production architecture.


95. System Design Connection#

At this point, SQL should no longer feel like just a programming language.

Think of the complete path:

Client
  |
  v
API
  |
  v
Application
  |
  v
Connection Pool
  |
  v
SQL Query
  |
  v
Query Planner
  |
  v
Index / Scan
  |
  v
Database Storage

Every layer can influence latency.

For example:

Poor query
   ↓
Large scan
   ↓
High CPU/I/O
   ↓
Higher query latency
   ↓
Connections stay occupied
   ↓
Pool saturation
   ↓
API latency
   ↓
User-visible slowdown

This is why SQL knowledge is directly relevant to system design.


96. Chapter Summary#

You now understand the core SQL capabilities required for database-focused system design.

The major concepts are:

SQL
 |
 +-- DDL
 |    +-- CREATE
 |    +-- ALTER
 |    +-- DROP
 |
 +-- DML
 |    +-- INSERT
 |    +-- UPDATE
 |    +-- DELETE
 |
 +-- Queries
 |    +-- SELECT
 |    +-- WHERE
 |    +-- ORDER BY
 |    +-- LIMIT
 |
 +-- Aggregation
 |    +-- COUNT
 |    +-- SUM
 |    +-- AVG
 |    +-- GROUP BY
 |    +-- HAVING
 |
 +-- Relationships
 |    +-- JOIN
 |    +-- Subquery
 |    +-- CTE
 |
 +-- Transactions
 |    +-- BEGIN
 |    +-- COMMIT
 |    +-- ROLLBACK
 |
 +-- Performance
      +-- Index
      +-- EXPLAIN
      +-- Query Plan
      +-- Pagination
      +-- N+1

The most important system-design lesson is:

A SQL query is not just a piece of application code. At scale, its execution strategy, indexes, concurrency, connection usage, and data volume can determine the performance of the entire system.


97. Master Checklist#

Before moving forward, you should be able to explain:

  • What SQL is
  • Declarative query language
  • Database schema
  • SQL data types
  • CREATE TABLE
  • INSERT
  • SELECT
  • UPDATE
  • DELETE
  • WHERE
  • AND / OR / NOT
  • IN
  • BETWEEN
  • LIKE
  • NULL
  • ORDER BY
  • LIMIT
  • OFFSET pagination
  • Cursor/keyset pagination
  • COUNT
  • SUM
  • AVG
  • GROUP BY
  • HAVING
  • INNER JOIN
  • LEFT JOIN
  • Self JOIN
  • Many-to-many JOIN
  • Subqueries
  • EXISTS
  • CTEs
  • Window functions
  • Constraints
  • Foreign keys
  • Referential actions
  • Transactions
  • Isolation levels
  • Query planner
  • Sequential scan
  • Index scan
  • EXPLAIN
  • EXPLAIN ANALYZE
  • Composite indexes
  • Covering indexes
  • Index trade-offs
  • N+1 query problem
  • SQL injection
  • Parameterized queries
  • ORM vs SQL
  • Connection-pool impact
  • Query performance troubleshooting

98. What Comes Next?#

Chapter 11 — NoSQL Databases#

Next we move beyond relational databases and study NoSQL systems in depth.

We will learn:

Why NoSQL exists
        ↓
NoSQL data models
        ↓
Document databases
        ↓
Key-value databases
        ↓
Wide-column databases
        ↓
Graph databases
        ↓
Schema design
        ↓
Access-pattern-first modeling
        ↓
Consistency models
        ↓
Partitioning
        ↓
Replication
        ↓
Distributed NoSQL architecture
        ↓
SQL vs NoSQL trade-offs
        ↓
Real-world database selection

We will also examine when a system should use SQL, when NoSQL makes sense, and why large distributed systems sometimes use multiple database technologies together.

Finished this chapter?

Mark it as complete to track your mastery.

Previous ChapterChapter 09 — Database FundamentalsNext ChapterChapter 11 — NoSQL Databases
PrevNext