1. Introduction & Logical SQL Query Execution Order
In technical interviews for Data Engineering, Backend Development, and Full-Stack Engineering, SQL is one of the most rigorously tested skills. Many developers write SQL intuitively based on syntax, but fail interview questions because they do not understand how the database engine executes queries under the hood.
While you write SQL starting with SELECT, the SQL engine evaluates clauses in a strict mathematical order:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LOGICAL SQL EXECUTION ORDER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. FROM ββ> Identify source tables and virtual table buffers β
β 2. ON ββ> Evaluate join conditions for each candidate row pair β
β 3. JOIN ββ> Materialize Joined Table (INNER, LEFT, RIGHT, FULL) β
β 4. WHERE ββ> Filter individual rows BEFORE grouping β
β 5. GROUP BY ββ> Collapse rows into distinct group partitions β
β 6. HAVING ββ> Filter aggregated group metrics (AFTER grouping) β
β 7. SELECT ββ> Compute output expressions, subqueries, column aliases β
β 8. DISTINCT ββ> Deduplicate resulting rows β
β 9. ORDER BY ββ> Sort final records (can use SELECT aliases) β
β 10. LIMIT/OFF ββ> Slice row offset window for client response β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SELECT alias inside a WHERE clause? Because WHERE (Step 4) runs before SELECT (Step 7) computes the alias! However, you can use aliases in ORDER BY (Step 9) because sorting occurs after column projection.
2. SQL Joins Masterclass: Mechanics & Anti-Patterns
Relational algebra combines datasets based on predicate matching. Understanding the nuances between join types is essential for both performance and data correctness.
| Join Type | Description | Unmatched Left Rows | Unmatched Right Rows |
|---|---|---|---|
INNER JOIN |
Intersection of both tables matching ON predicate | Discarded | Discarded |
LEFT JOIN |
All left rows + matched right rows (NULL if no match) | Preserved with NULLs | Discarded |
RIGHT JOIN |
All right rows + matched left rows (NULL if no match) | Discarded | Preserved with NULLs |
FULL OUTER JOIN |
Union of left and right datasets with NULL padding | Preserved with NULLs | Preserved with NULLs |
CROSS JOIN |
Cartesian product ($N \times M$ rows, no ON condition) | Multiplied | Multiplied |
SELF JOIN |
Table joined to itself using aliases (hierarchies/pairs) | Depends on Join Type | Depends on Join Type |
The Anti-Join Pattern: Finding Missing Records
-- Pattern 1: LEFT JOIN with IS NULL (Highly efficient with indexes)
SELECT c.customer_id, c.customer_name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
-- Pattern 2: NOT EXISTS (Best optimizer performance with subquery)
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
-- β οΈ WARNING: 'NOT IN' FAILS SILENTLY IF SUBQUERY CONTAINS NULL VALUES!
-- If any o.customer_id is NULL, 'NOT IN' evaluates to UNKNOWN and returns 0 rows!
3. Window Functions: The #1 Most Tested SQL Interview Topic
Unlike GROUP BY which collapses multiple rows into a single aggregated summary row, Window Functions compute values across a sliding partition of rows while preserving each individual row's identity.
1. Ranking Functions: ROW_NUMBER vs RANK vs DENSE_RANK
SELECT
employee_id,
department_id,
salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS row_num,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dense_rnk
FROM employees;
| Salary | ROW_NUMBER() |
RANK() |
DENSE_RANK() |
Explanation |
|---|---|---|---|---|
| $120,000 | 1 | 1 | 1 | Highest salary |
| $100,000 (Tie) | 2 | 2 | 2 | First tied employee |
| $100,000 (Tie) | 3 | 2 | 2 | Second tied employee |
| $90,000 | 4 | 4 (skips 3) | 3 (no gaps) | Notice RANK skips numbers after ties! |
2. Value & Offset Functions: LEAD, LAG, FIRST_VALUE
-- Month-Over-Month (MoM) Revenue Growth Calculation
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', order_date) AS order_month,
SUM(order_total) AS total_revenue
FROM orders
GROUP BY 1
)
SELECT
order_month,
total_revenue,
LAG(total_revenue, 1) OVER (ORDER BY order_month) AS previous_month_revenue,
ROUND(
(total_revenue - LAG(total_revenue, 1) OVER (ORDER BY order_month))::numeric
/ NULLIF(LAG(total_revenue, 1) OVER (ORDER BY order_month), 0) * 100.0, 2
) AS mom_growth_pct
FROM monthly_revenue;
3. Running Totals & Moving Averages
-- Cumulative Running Total per Customer
SELECT
customer_id,
order_date,
amount,
SUM(amount) OVER (
PARTITION BY customer_id
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_spend,
AVG(amount) OVER (
PARTITION BY customer_id
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_3_order_avg
FROM customer_orders;
4. Common Table Expressions (CTEs) & Recursive SQL
CTEs (WITH ... AS) improve query readability and modularity. Recursive CTEs solve graph and hierarchical tree problems (such as corporate reporting structures or bill of materials).
-- Organizational Hierarchy: Find all subordinates under Manager (CEO ID = 1)
WITH RECURSIVE OrgHierarchy AS (
-- 1. Anchor Member (Base Case: CEO)
SELECT
employee_id,
first_name,
manager_id,
1 AS org_level,
first_name::text AS path
FROM employees
WHERE employee_id = 1
UNION ALL
-- 2. Recursive Member (Join back to CTE)
SELECT
e.employee_id,
e.first_name,
e.manager_id,
h.org_level + 1,
h.path || ' -> ' || e.first_name
FROM employees e
INNER JOIN OrgHierarchy h ON e.manager_id = h.employee_id
)
SELECT * FROM OrgHierarchy ORDER BY org_level, employee_id;
5. Database Indexing & Query Optimization Internals
Senior database interviews test your understanding of hardware I/O, B-Tree indexes, and why queries run slowly in production.
1. B-Tree Index Architecture
- Root & Branch Nodes: Store key pointers to navigate large datasets in O(log N) time.
- Leaf Nodes: Linked double-ended lists containing physical tuple IDs (Heap pointers) or clustered row data.
- Range Scans: B-Trees excel at
=,<,>,BETWEEN, andORDER BY.
2. The Leftmost Prefix Rule on Composite Indexes
If you create a composite index on CREATE INDEX idx_user_status_date ON users(country_code, status, created_at);
WHERE country_code = 'US' AND status = 'ACTIVE'→ Uses Index (Full Speed)WHERE country_code = 'US'→ Uses Index (Leading column)WHERE status = 'ACTIVE'→ Full Table Scan! (Leftmost column missing)
3. SARGability (Search Argument Able)
Wrapping indexed columns in functions disables index lookup and forces full table scans:
-- β BAD: Non-SARGable (Forces Full Table Scan on 50 Million rows)
SELECT * FROM orders WHERE YEAR(order_date) = 2026;
-- β
GOOD: SARGable (Utilizes B-Tree Range Scan Index)
SELECT * FROM orders WHERE order_date >= '2026-01-01' AND order_date < '2027-01-01';
-- β BAD: Expression on Column
SELECT * FROM products WHERE price * 1.18 > 1000;
-- β
GOOD: Expression moved to Constant literal
SELECT * FROM products WHERE price > (1000 / 1.18);
6. ACID Transactions, Concurrency & Isolation Levels
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
READ UNCOMMITTED |
Yes (Permitted) | Yes | Yes |
READ COMMITTED (Default in Postgres) |
No (Prevented) | Yes | Yes |
REPEATABLE READ (Default in MySQL InnoDB) |
No | No | No (via MVCC) |
SERIALIZABLE |
No | No | No |
7. Top 10 High-Frequency SQL Coding Interview Problems
Problem 1: Find the Nth Highest Salary
-- Solution 1: Using DENSE_RANK (Handles duplicate ties gracefully)
WITH RankedSalaries AS (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT DISTINCT salary
FROM RankedSalaries
WHERE rnk = 2; -- Change '2' to Nth
-- Solution 2: Using LIMIT / OFFSET (Simple single value)
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1; -- For 2nd highest, OFFSET = N - 1
Problem 2: Department Top 3 Salaries (LeetCode #185)
WITH RankedDeptSalaries AS (
SELECT
d.name AS Department,
e.name AS Employee,
e.salary AS Salary,
DENSE_RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS rnk
FROM employees e
INNER JOIN departments d ON e.department_id = d.id
)
SELECT Department, Employee, Salary
FROM RankedDeptSalaries
WHERE rnk <= 3;
Problem 3: Find Consecutive Active Logins (3 or More Days)
WITH DateGrouped AS (
SELECT
user_id,
login_date,
login_date - (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date))::int AS grp
FROM user_logins
GROUP BY user_id, login_date -- deduplicate same day logins
)
SELECT
user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS consecutive_days
FROM DateGrouped
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
Problem 4: Delete Duplicate Rows While Keeping the Smallest ID
-- Solution 1: Using DELETE with Self-Join
DELETE FROM customers
WHERE id IN (
SELECT c1.id
FROM customers c1
INNER JOIN customers c2 ON c1.email = c2.email AND c1.id > c2.id
);
-- Solution 2: Using CTE with ROW_NUMBER (PostgreSQL / SQL Server)
WITH Duplicates AS (
SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM customers
)
DELETE FROM customers
WHERE id IN (SELECT id FROM Duplicates WHERE rn > 1);
Problem 5: Employees Earning More Than Their Immediate Managers
SELECT
e.name AS Employee,
e.salary AS EmployeeSalary,
m.name AS Manager,
m.salary AS ManagerSalary
FROM employees e
INNER JOIN employees m ON e.manager_id = m.id
WHERE e.salary > m.salary;
8. Complete SQL Interview Rapid Recall Cheatsheet
- β Execution Order: FROM → ON → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT.
- β WHERE vs HAVING: WHERE filters raw records before grouping; HAVING filters aggregated calculations ($SUM, COUNT, AVG$).
- β COUNT(*) vs COUNT(col):
COUNT(*)counts all rows including NULLs;COUNT(col)counts only non-NULL rows. - β NULL Comparisons: Always use
IS NULLorIS NOT NULL;col = NULLevaluates to UNKNOWN and never returns true. - β UNION vs UNION ALL:
UNIONperforms an expensive distinct sorting sort to remove duplicates;UNION ALLappends rows immediately in $O(1)$ time. - β DENSE_RANK vs RANK:
DENSE_RANKleaves no numerical gaps after ties ($1, 2, 2, 3$);RANKskips numbers ($1, 2, 2, 4$). - β COALESCE:
COALESCE(val1, val2, default)returns the first non-NULL expression from left to right. - β NULLIF:
NULLIF(val, 0)converts 0 to NULL to prevent division by zero runtime crashes (division by zeroerror). - β TRUNCATE vs DELETE vs DROP:
DELETEis DML (row by row, logged, rollbackable);TRUNCATEis DDL (deallocates pages, instant, resets auto-increment);DROPdestroys table structure completely. - β B-Tree vs Hash Index: B-Tree handles range queries ($>, <, BETWEEN$); Hash index only supports exact equality ($=$) lookups.