SQL Cheat Sheet

CNF practical prep · everything we covered + key gaps

Quick Reference

If you remember only 5 things:

  • Execution order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
  • NULL = NULL returns UNKNOWN. Always use IS NULL.
  • WHERE filters rows; HAVING filters groups.
  • JOIN ... ON, not JOIN ... WHERE for the join condition.
  • Aliases work in ORDER BY but NOT in WHERE.

📦Database Basics

TermMeaning
DatabaseCollection of related tables
TableGrid of rows and columns (like a spreadsheet)
Row (record)One entity instance (one customer, one order)
Column (field)One attribute (name, age, salary)
Primary Key (PK)Unique identifier per row · NOT NULL + UNIQUE · one per table
Foreign Key (FK)Column pointing to PK in another table · enforces referential integrity
Composite KeyPK made of multiple columns
RDBMSRelational DB Management System (MySQL, PostgreSQL, Oracle, SQL Server, SQLite)

🗂️SQL Command Categories

TypeStands forCommandsPurpose
DDLData DefinitionCREATE ALTER DROP TRUNCATE RENAMEStructure
DMLData ManipulationINSERT UPDATE DELETEData changes
DQLData QuerySELECTRead data
DCLData ControlGRANT REVOKEPermissions
TCLTransaction ControlCOMMIT ROLLBACK SAVEPOINTTransactions
🎯 Interview trap: TRUNCATE looks like DML but is DDL — it deallocates pages, can't be rolled back, resets identity. DELETE is DML — row-by-row, logged, can be rolled back.
CRUD = Create Read Update Delete = INSERT SELECT UPDATE DELETE

Execution Order CRITICAL

You write SQL in one order. The database runs it in a completely different order.

#StepWhat it does
1FROM / JOINGet rows from tables
2WHEREFilter individual rows
3GROUP BYGroup rows together
4HAVINGFilter groups
5SELECTPick columns, compute expressions, assign aliases
6ORDER BYSort the result
7LIMITTrim to N rows
Memory: "From Where Groups Have Selected Ordered Limits"
Why this matters:
WHERE can't use SELECT aliases (alias doesn't exist yet)
ORDER BY CAN use SELECT aliases (it runs after)
WHERE can't use aggregate functions (no groups yet) — use HAVING

📝SELECT — the master skeleton

SELECT    column1, column2, COUNT(*)
FROM      table1
JOIN      table2 ON table1.id = table2.fk
WHERE     row_filter_condition
GROUP BY  column1
HAVING    group_filter_condition
ORDER BY  column1 ASC, column2 DESC
LIMIT     10;

Each clause is optional except SELECT. Use only what you need.

🔍WHERE Operators

OperatorUseExample
= != <>Equal / not equalcity = 'Lahore'
> < >= <=Comparisonamount >= 5000
BETWEEN x AND yRange, inclusive both endsage BETWEEN 18 AND 65
IN (a, b, c)Match any in listcity IN ('Lahore', 'Multan')
NOT INNone in liststatus NOT IN ('cancelled')
LIKEPattern: %=any chars, _=one charname LIKE 'A%'
IS NULLCheck NULLphone IS NULL
IS NOT NULLCheck non-NULLphone IS NOT NULL
AND OR NOTCombine conditionsx > 5 AND y < 10
🎯 NEVER write = NULL — it always returns no rows. Use IS NULL.
AND > OR precedence: A OR B AND C means A OR (B AND C). Use parentheses to be safe.
LIKE patterns: 'A%' starts with A · '%A' ends with A · '%A%' contains A · '_A%' A is 2nd character

🔢ORDER BY · LIMIT

SELECT name, salary
FROM Employees
ORDER BY salary DESC, name ASC
LIMIT 5;
  • ASC = ascending (default, small→large)
  • DESC = descending (large→small)
  • Can sort by multiple columns — secondary sort breaks ties
  • LIMIT N = first N rows · LIMIT N OFFSET M = skip M then take N
  • ORDER BY can use SELECT aliases (e.g., ORDER BY total DESC)
LIMIT without ORDER BY = unpredictable. Different runs may give different rows.

🔗JOINs — all six types

TypeReturnsUse when
INNER JOINOnly matched rowsYou want pairs that exist on both sides
LEFT JOINAll from left + matches from right (NULL if none)"Show all customers, with orders if any"
RIGHT JOINAll from right + matches from leftMirror of LEFT — rarely used
FULL OUTER JOINAll rows from both, NULL fills gapsShow everything from both sides
CROSS JOINCartesian product (every × every)Rare — usually a bug
SELF JOINTable joined to itselfHierarchies: employees + their managers
-- Basic INNER JOIN
SELECT c.name, o.amount
FROM Customers c
INNER JOIN Orders o ON c.customer_id = o.customer_id;

-- Find customers who never ordered (LEFT JOIN trick)
SELECT c.name
FROM Customers c
LEFT JOIN Orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;

-- SELF JOIN: each employee with their manager
SELECT e.name AS employee, m.name AS manager
FROM Employees e
LEFT JOIN Employees m ON e.manager_id = m.emp_id;
🎯 Use ON for join conditions, NOT WHERE. WHERE filters AFTER the join.
Pattern — "find rows with no match": LEFT JOIN + WHERE right_side_col IS NULL

📊GROUP BY · Aggregates · HAVING

FunctionReturnsNULL behavior
COUNT(*)Count of rowsCounts ALL rows including NULL
COUNT(col)Count of non-NULL valuesSkips NULL
COUNT(DISTINCT col)Count of unique valuesSkips NULL
SUM(col)TotalSkips NULL · returns NULL if all NULL or empty
AVG(col)MeanSkips NULL — denominator changes!
MIN(col) / MAX(col)Smallest / largestSkips NULL · works on numbers, dates, strings
SELECT department, COUNT(*), AVG(salary)
FROM Employees
WHERE status = 'active'      -- filter rows BEFORE grouping
GROUP BY department
HAVING COUNT(*) > 5;     -- filter groups AFTER aggregation
🎯 Rule: Every non-aggregated column in SELECT must appear in GROUP BY.
Wrong: SELECT name, COUNT(*) FROM emp GROUP BY deptname isn't grouped.
WHERE vs HAVING: WHERE for individual rows. HAVING for grouped results. WHERE can't use aggregates; HAVING can.
Empty set behavior:
COUNT(*)0
SUM/AVG/MIN/MAXNULL (not 0!)
Wrap with COALESCE(SUM(x), 0) to force 0.

🪆Subqueries

Scalar subquery — returns one value

-- Employees above company average
SELECT name, salary
FROM Employees
WHERE salary > (SELECT AVG(salary) FROM Employees);

IN-list subquery — returns a list

SELECT name FROM Customers
WHERE customer_id IN (SELECT customer_id FROM Orders WHERE amount > 5000);

Correlated subquery — references outer query

-- Employees earning above their OWN department average
SELECT name, salary
FROM Employees e1
WHERE salary > (
    SELECT AVG(salary) FROM Employees e2
    WHERE e2.dept_id = e1.dept_id   -- references outer row
);
EXISTS vs IN: Both work for "matches in another table." EXISTS is often faster for big tables. EXISTS (SELECT 1 FROM ... WHERE ...)

🧱CTEs (WITH clause)

A CTE is a named temporary result set. Use it to break complex queries into readable layers.

WITH customer_totals AS (
    SELECT customer_id, city, SUM(amount) AS total
    FROM Orders JOIN Customers USING(customer_id)
    GROUP BY customer_id
)
SELECT name, total
FROM customer_totals
WHERE total > (
    SELECT AVG(total) FROM customer_totals
);
Why CTEs: Avoid repeating the same subquery, build queries in layers, easier to debug. Equivalent to subqueries in performance for most databases.

UNION · DISTINCT · Set Ops

OperationEffect
UNIONCombine rows, remove duplicates (slower)
UNION ALLCombine rows, keep all duplicates (faster)
INTERSECTOnly rows in both queries
EXCEPT / MINUSRows in first query but not in second
DISTINCTRemove duplicate rows from a single query
SELECT name FROM Customers
UNION
SELECT name FROM Suppliers;

SELECT DISTINCT city FROM Customers;
UNION rules: Both queries must have the same number of columns with compatible data types.

⚠️NULL Handling CRITICAL

NULL means "unknown value" — not zero, not empty string. Special rules apply.

ExpressionResult
NULL = NULLUNKNOWN (treated as FALSE)
NULL = 5UNKNOWN
NULL != 5UNKNOWN
5 + NULLNULL
'A' || NULL (concat)NULL (or 'A' in Oracle)
WHERE phone = NULLAlways 0 rows — wrong!
WHERE phone IS NULLCorrect way to check

Replacing NULL with a default

COALESCE(column, default_value)        -- ANSI standard, multiple args
IFNULL(column, default_value)          -- MySQL, SQLite
NVL(column, default_value)             -- Oracle
ISNULL(column, default_value)          -- SQL Server
NULL ≠ empty string. WHERE x IS NULL won't match rows where x = ''. To catch both: WHERE x IS NULL OR x = ''.

🔐Keys & Constraints

ConstraintWhat it enforcesNULL allowed?
PRIMARY KEYUnique + non-null · 1 per tableNo
FOREIGN KEYMust match a PK in parent tableYes
UNIQUENo duplicates · multiple allowed per tableYes (usually 1 NULL)
NOT NULLValue must be providedNo
CHECK (cond)Custom Boolean conditionDepends on condition
DEFAULT valFallback when no value given
CREATE TABLE Employees (
    emp_id   INTEGER PRIMARY KEY,
    name     TEXT NOT NULL,
    email    TEXT UNIQUE,
    dept_id  INTEGER,
    salary   REAL CHECK (salary > 0),
    status   TEXT DEFAULT 'active',
    FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
        ON DELETE SET NULL ON UPDATE CASCADE
);
FK behaviors: CASCADE (propagate), SET NULL (clear FK), RESTRICT/NO ACTION (block change).

🧬Normalization

FormRule (in plain English)
1NFOne value per cell. No lists, no repeating groups. (atomic)
2NF1NF + no partial dependencies. (Only relevant for composite PKs — every non-key column must depend on the FULL key.)
3NF2NF + no transitive dependencies. (No non-key column depends on another non-key column.)
BCNF3NF + every determinant is a candidate key. (Edge case of 3NF.)
Codd's line: Every non-key attribute depends on "the key, the whole key, and nothing but the key, so help me Codd."
• The key → 1NF
• The whole key → 2NF
• Nothing but the key → 3NF
Single-column PK? Then your table is automatically in 2NF (no part of key to partially depend on).

Anomalies normalization prevents

  • Insert anomaly — can't add data without dummy values
  • Update anomaly — same fact stored in multiple rows; must update everywhere
  • Delete anomaly — deleting one row loses unrelated info

📇Indexes

TypeHow it worksPer table
ClusteredPhysically orders rows by index column · PK usually creates this1 max
Non-clusteredSeparate structure with pointers to rowsMany allowed
B-tree (default)Supports equality + range queries · balanced tree
HashEquality only · no range queries
CompositeIndex on multiple columns · order matters
UniqueEnforces uniqueness + speeds up lookups
When to index: columns frequently in WHERE, JOIN, ORDER BY, or with high uniqueness.
Cost of indexes: Every INSERT/UPDATE/DELETE updates every relevant index. 10 indexes ≈ 10× slower writes. Don't over-index.

🛠️Views · Triggers · Procedures · Functions

ObjectWhat it isHow invoked
ViewStored SELECT query — virtual tableQueried like a table
Stored ProcedureNamed code block · can have parameters · multiple statementsCALL proc_name(args)
FunctionReturns one value · used inside expressionsSELECT my_func(x)
TriggerAuto-runs BEFORE/AFTER INSERT/UPDATE/DELETEAutomatic on event
Sequence / Auto-incrementAuto-generates unique IDsUsed in INSERT
-- View
CREATE VIEW active_customers AS
SELECT id, name, email FROM Customers WHERE status = 'active';

-- Trigger (logs every salary change)
CREATE TRIGGER log_salary_change
AFTER UPDATE ON Employees
FOR EACH ROW
BEGIN
    INSERT INTO audit_log VALUES (OLD.emp_id, OLD.salary, NEW.salary, NOW());
END;

🛡️ACID Properties

LetterPropertyGuaranteesMemory trigger
AAtomicityAll steps succeed or all are rolled back"All or nothing"
CConsistencyDatabase moves from valid state to valid state"No broken rules"
IIsolationConcurrent transactions don't see each other's incomplete work"Two users at once"
DDurabilityCommitted changes survive crashes"Survives power cut"
The bank transfer story: Ahmad sends 3000 to Sara.
A: if crediting Sara fails, debiting Ahmad is reversed.
C: total money in the bank stays the same.
I: two simultaneous transfers don't corrupt each other.
D: once committed, a power cut doesn't undo it.
NoSQL alternative: BASE = Basically Available, Soft state, Eventual consistency. Trades correctness for scale.

💸Transactions

BEGIN TRANSACTION;     -- or START TRANSACTION
    UPDATE accounts SET balance = balance - 3000 WHERE name = 'Ahmad';
    UPDATE accounts SET balance = balance + 3000 WHERE name = 'Sara';
COMMIT;                  -- save permanently
-- or ROLLBACK to undo everything since BEGIN

SAVEPOINT — partial rollback

BEGIN TRANSACTION;
    UPDATE ...;
    SAVEPOINT step1;
    UPDATE ...;          -- if this is wrong:
ROLLBACK TO step1;       -- undoes only after the savepoint
COMMIT;

🔒Isolation Levels & Read Phenomena

The 3 read anomalies

AnomalyWhat happens
Dirty readRead another transaction's uncommitted changes
Non-repeatable readSame row read twice → different values (someone updated it)
Phantom readSame query returns different rows (someone inserted new ones)

4 isolation levels (weakest → strongest)

LevelDirtyNon-repPhantom
Read Uncommitted
Read Committed PG, Oracle default
Repeatable Read MySQL default
Serializable

✅ = prevents the anomaly · ❌ = allows it

🎯Top 15 Gotchas EXAM

  1. NULL = NULL returns UNKNOWN — always use IS NULL
  2. WHERE filters rows; HAVING filters groups
  3. SELECT aliases NOT available in WHERE (WHERE runs before SELECT)
  4. SELECT aliases ARE available in ORDER BY (runs after SELECT)
  5. INNER JOIN uses ON, not WHERE for the join condition
  6. GROUP BY: every non-aggregated column in SELECT must be grouped
  7. SUM/AVG/MIN/MAX on empty/all-NULL = NULL, not 0
  8. AVG ignores NULLs — both numerator AND denominator change
  9. COUNT(*) counts NULLs; COUNT(column) doesn't
  10. LIMIT without ORDER BY = unpredictable rows
  11. TRUNCATE is DDL (no rollback); DELETE is DML (can rollback)
  12. CROSS JOIN (no ON) = Cartesian product — usually a bug
  13. Single quotes for text values: 'Lahore', not "Lahore"
  14. LIKE: % = any chars, _ = exactly one char
  15. UNION removes duplicates; UNION ALL keeps them (faster)

🎤Interview Q&A — prepared answers

Q: What's the difference between INNER JOIN and LEFT JOIN?
INNER JOIN returns only rows that have a match in both tables. LEFT JOIN returns all rows from the left table, plus matched rows from the right; if no match exists, right columns become NULL. Use LEFT when you need to keep unmatched rows.
Q: Primary key vs Foreign key?
Primary key uniquely identifies each row in a table — NOT NULL, UNIQUE, one per table. Foreign key references the primary key in another table, enforcing referential integrity — you can't insert an FK value that doesn't exist in the parent.
Q: What is normalization?
Restructuring tables to reduce redundancy and prevent anomalies. 1NF requires atomic values. 2NF removes partial dependencies (relevant for composite keys). 3NF removes transitive dependencies. Each form prevents update, insert, and delete anomalies.
Q: Explain ACID.
Four properties of reliable transactions. Atomicity — all or nothing. Consistency — moves from valid state to valid state. Isolation — concurrent transactions don't interfere. Durability — committed data survives crashes.
Q: DELETE vs TRUNCATE?
DELETE is DML, removes rows individually, logged, can be rolled back, keeps identity counter. TRUNCATE is DDL, deallocates entire table pages, can't be rolled back in most engines, resets identity, much faster. Use TRUNCATE for clearing whole tables.
Q: What is an index? When would you use one?
A sorted lookup structure that speeds up queries on indexed columns. Use on columns frequently used in WHERE, JOIN, or ORDER BY. Cost is slower writes and extra storage. Most use B-trees, which support both equality and range queries.
Q: WHERE vs HAVING?
WHERE filters individual rows before grouping. HAVING filters groups after aggregation. WHERE can't use aggregate functions; HAVING can. WHERE runs earlier in execution order.
Q: What's a stored procedure?
A named, pre-compiled block of SQL stored in the database, callable by name. Benefits: faster execution due to pre-compilation, code reuse, security through permission control, reduced network traffic.
Q: View vs Table?
A table physically stores data. A view is a stored SELECT query that behaves like a virtual table — no data is duplicated, the underlying query runs each time you query the view. Used for security (hide columns) and simplification (hide complex joins).
Q: How would you find duplicate rows?
Use GROUP BY on the columns that define duplication, with HAVING COUNT(*) > 1: SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
Q: Explain a transaction with a real example.
A bank transfer of 3000 from Ahmad to Sara. Two UPDATE statements: subtract from Ahmad, add to Sara. Both must succeed together. If either fails, ROLLBACK reverses both. COMMIT makes both permanent. Without transactions, a failure between the two updates would lose 3000 from the system.
Q: What's the difference between clustered and non-clustered indexes?
A clustered index physically orders the table rows by the index column — only one per table because rows can only be physically sorted one way. A non-clustered index is a separate structure with pointers back to rows — many allowed per table.

📚Bonus topics — quick mention

CASE WHEN — inline if/else

SELECT name,
       CASE
           WHEN salary < 50000 THEN 'Junior'
           WHEN salary < 100000 THEN 'Mid'
           ELSE 'Senior'
       END AS level
FROM Employees;

Window functions — aggregate without collapsing rows

SELECT name, salary,
       AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg,
       RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employees;

Common window functions: ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), LEAD(), SUM/AVG/COUNT OVER(...).

Date functions (vary by database)

NOW()                   -- current timestamp
CURRENT_DATE            -- today's date
DATE('2024-01-15')      -- cast to date
YEAR(date_col)         -- extract year
DATEDIFF(d1, d2)       -- difference in days

String functions

UPPER(s) LOWER(s)      -- case conversion
LENGTH(s)              -- string length
SUBSTR(s, start, len)  -- substring
TRIM(s)                -- remove leading/trailing spaces
CONCAT(s1, s2) or s1 || s2  -- combine
REPLACE(s, old, new)   -- replace substring

NoSQL families (recognition only)

TypeExample
DocumentMongoDB, CouchDB
Key-valueRedis, DynamoDB
Column-familyCassandra, HBase
GraphNeo4j