The 40 SQL and DBMS questions that come up most in interviews, built on one small company database. Each one has a short answer you can say out loud in about 30 seconds, and the full explanation, with the query and its result, for when you need it.
Every query on this page runs against this small company database. The data is deliberately awkward: it has a top boss with no manager (Asha), an employee with no department (Suresh), a department with no employees (Finance), two employees on the same salary (Priya and Arjun), two customers sharing an email (the two Rahuls), customers with no orders (Fatima and Deepak), and orders saved twice or three times (104 and 105; 107, 108 and 111). Those are exactly the cases interview questions probe.
Who reports to whom: Asha manages Ravi, Sneha, Meera and Suresh; Ravi manages Priya; Priya manages Arjun; Sneha manages Vikram and Sana; Meera manages Sameer.
01DBMS basics
0/3 got it
Q01Not marked yet·Fresher·Asked very often·Concept
What is a DBMS, and how is an RDBMS different?
Say this first~30 sec to say
A DBMS (database management system) is software that stores, retrieves and manages data for you, handling concurrency, security, backups and crash recovery, instead of every program juggling its own files. An RDBMS is a DBMS that keeps data in tables of rows and columns, links tables through keys, enforces constraints, and is queried with SQL. MySQL, PostgreSQL, Oracle and SQL Server are RDBMSs; MongoDB and Redis are DBMSs that aren't relational.
Could you say it?
Full answerHide full answer2 min read
Why not just use files?
Problem with plain files
What a DBMS does
Two programs writing at once corrupt the data
Concurrency control and locking
A crash halfway through an update leaves half-written data
Transactions and recovery logs
The same data copied into many files drifts apart
One shared, consistent store
Finding records means reading whole files
Indexes and a query language
Anyone who can open the file sees everything
Users, permissions and auditing
DBMS
RDBMS
Data stored as
Files, documents, key-value pairs, graphs or tables
The usual warm-up, "SQL vs MySQL": SQL is a language, standardised by ANSI and ISO. MySQL is one product, an RDBMS, that implements SQL along with its own extensions, such as LIMIT and AUTO_INCREMENT.
Follow-ups
What is a schema? The structure of the database: its tables, columns, types, keys and constraints, as opposed to the data in them.
Is MongoDB a DBMS? Yes, a document-oriented DBMS. It isn't relational: it stores JSON-like documents and doesn't enforce joins and foreign keys the same way.
Q02Not marked yet·Fresher·Asked very often·Concept
What are DDL, DML, DCL and TCL, and which commands belong to each?
Say this first~25 sec to say
SQL commands fall into groups by what they act on. DDL (data definition) changes the structure: CREATE, ALTER, DROP, TRUNCATE, RENAME. DML (data manipulation) changes the rows: INSERT, UPDATE, DELETE. DCL (data control) manages permissions: GRANT, REVOKE. TCL (transaction control) groups changes into transactions: COMMIT, ROLLBACK, SAVEPOINT. Many lists also give SELECT its own group, DQL (data query); others count it as DML.
Could you say it?
Full answerHide full answer2 min read
Group
Stands for
Commands
Acts on
DDL
Data Definition Language
CREATE, ALTER, DROP, TRUNCATE, RENAME
Tables, indexes, views: the structure
DML
Data Manipulation Language
INSERT, UPDATE, DELETE (some include SELECT)
The data in tables
DQL
Data Query Language
SELECT
Reading data
DCL
Data Control Language
GRANT, REVOKE
Users' permissions
TCL
Transaction Control Language
COMMIT, ROLLBACK, SAVEPOINT, START TRANSACTION
Transactions
SQL
-- DDL: change the structureALTERTABLE employees ADDCOLUMN phone VARCHAR(15);
-- DML: change the dataUPDATE employees SET salary = salary * 1.10WHERE dept_id = 2;
-- DCL: change who may do whatGRANTSELECTON company.orders TO'report_user'@'%';
-- TCL: make changes permanent, or undo themSTARTTRANSACTION;
DELETEFROM orders WHERE status = 'cancelled';
ROLLBACK; -- the cancelled orders are back
Why the grouping matters: in MySQL, DDL statements commit implicitly, so they end any open transaction and can't be rolled back. DML inside a transaction can be. That's the root of the DELETE vs TRUNCATE difference (question 8).
Follow-ups
Is TRUNCATE DDL or DML? DDL: it deallocates and recreates the table's storage rather than deleting rows one by one.
What does GRANT ALL do? Gives every privilege on the named objects. In production, grant only what each application user needs.
What does an ER diagram show, and how do one-to-one, one-to-many and many-to-many relationships become tables?
Say this first~30 sec to say
An ER (entity–relationship) diagram models data before you build tables: entities (things such as Employee and Department), their attributes (name, salary), and the relationships between them, with cardinality: how many of one relate to how many of the other. In tables: a one-to-many relationship puts a foreign key on the "many" side; a one-to-one puts a unique foreign key on either side; a many-to-many needs a junction table holding both keys.
Could you say it?
Full answerHide full answer2 min read
Mapping each relationship to tables
Relationship
Example
Tables
One-to-many
One department has many employees
employees.dept_id → departments.dept_id
One-to-one
One employee has one company laptop
laptops.emp_id UNIQUE → employees.emp_id
Many-to-many
Employees work on many projects; projects have many employees
A junction table employee_projects (emp_id, project_id)
Recursive (self)
An employee's manager is an employee
employees.manager_id → employees.emp_id
SQL
-- The many-to-many case: a junction tableCREATETABLEprojects (
project_id INTAUTO_INCREMENTPRIMARYKEY,
name VARCHAR(100) NOTNULL
);
CREATETABLEemployee_projects (
emp_id INTNOTNULL,
project_id INTNOTNULL,
role VARCHAR(50), -- attributes of the relationship itself live herePRIMARYKEY (emp_id, project_id), -- a composite key: one row per pairingFOREIGNKEY (emp_id) REFERENCESemployees (emp_id),
FOREIGNKEY (project_id) REFERENCESprojects (project_id)
);
ER notation in brief: rectangles for entities, ovals for attributes (underlined for keys), diamonds for relationships, and 1, N or M on the lines for cardinality. Crow's-foot notation draws the "many" end as a three-pronged foot.
Follow-ups
What is a weak entity? One that can't be identified by its own attributes alone, such as an order line identified only together with its order. It takes part of its key from its owner.
What are composite, multivalued and derived attributes? Composite: made of parts (an address of street, city, PIN). Multivalued: several values (phone numbers), which become a separate table. Derived: computed from others (age from date of birth), usually not stored.
02Keys and constraints
0/4 got it
Q04Not marked yet·Fresher·Asked very often·Concept
Primary key vs unique key: how do they differ?
Say this first~30 sec to say
Both reject duplicate values. A primary key is the table's main identifier: there's only one per table, it can never be NULL, and other tables' foreign keys usually point at it; in InnoDB it also decides how the rows are physically stored. A table can have several unique keys, and they accept NULLs, because NULL isn't equal to anything, not even another NULL. In employees, emp_id is the primary key and email a unique key.
Could you say it?
Full answerHide full answer1 min read
Primary key
Unique key
How many per table
One (it may span several columns)
Any number
NULL allowed
No
Yes (how many depends on the database)
Duplicates
No
No
Index created
Yes; clustered in InnoDB (question 36)
Yes, a secondary index
Typical use
The row's identity, referenced by foreign keys
Natural identifiers that must stay unique: email, PAN, phone
NULLs in unique columns differ between databases: MySQL and PostgreSQL allow many NULLs in a unique column; SQL Server allows only one, unless you use a filtered index.
Follow-ups
Can a primary key span several columns? Yes, a composite primary key, such as (emp_id, project_id) in a junction table (question 6).
Natural or surrogate key? A natural key comes from the data (an email); a surrogate key is invented (an auto-increment ID). Surrogates are usually preferred as primary keys, because natural values can change.
Q05Not marked yet·Fresher·Asked very often·Concept
What does a foreign key enforce, and what happens to employees when their department is deleted under RESTRICT, CASCADE or SET NULL?
Say this first~30 sec to say
A foreign key enforces referential integrity: a value in the child column must exist in the parent's key, or be NULL if the column allows it, so employees.dept_id can't point at a department that doesn't exist. The ON DELETE rule decides what happens when a referenced parent row is deleted. RESTRICT (InnoDB's default) blocks the delete; CASCADE deletes the child rows too; SET NULL keeps the children but clears their reference.
Could you say it?
Full answerHide full answer2 min read
SQL
CREATETABLEemployees_demo (
emp_id INTPRIMARYKEY,
emp_name VARCHAR(100) NOTNULL,
dept_id INTNULL,
FOREIGNKEY (dept_id) REFERENCESdepartments (dept_id)
ONDELETESETNULL-- or RESTRICT (the default), or CASCADEONUPDATECASCADE
);
Deleting the Sales department (dept_id 2), which has three employees
Rule
What happens
RESTRICT / NO ACTION (default)
The delete fails: ERROR 1451: Cannot delete or update a parent row: a foreign key constraint fails. Move or delete the employees first
CASCADE
Sales and Sneha, Vikram and Sana are deleted
SET NULL
Sales is deleted; the three employees stay, with dept_id set to NULL
Choosing a rule:CASCADE suits parts that can't exist without their parent, such as an order's line items. SET NULL suits optional links. RESTRICT is the safe default when deleting the parent would be a mistake, as with a department that still has staff.
The usual opening version, "primary key vs foreign key":
Primary key
Foreign key
Purpose
Identifies each row in its own table
Links a row to a row in another (or the same) table
Unique
Yes
No: many employees share a dept_id
NULL
Never
Allowed if the column is nullable
Per table
One
Any number
Follow-ups
Can a foreign key reference its own table? Yes: employees.manager_id references employees.emp_id, a self-referencing key.
Does MySQL index foreign key columns? InnoDB requires an index on them and creates one automatically if none exists.
Q06Not marked yet·Fresher·Asked very often·Concept
How are super, candidate, primary, alternate and composite keys related? Use the employees table as the example
Say this first~30 sec to say
A super key is any set of columns that uniquely identifies a row, extra columns included: (emp_id, emp_name) works. A candidate key is a minimal super key, with nothing removable: in employees, both emp_id and email are candidates. The primary key is the candidate you choose, emp_id; the remaining candidates, here email, are alternate keys. A composite key is one made of several columns, like (emp_id, project_id) in a junction table.
Could you say it?
Full answerHide full answer1 min read
super keys ⊇ candidate keys ⊇ primary key
(any unique set) (minimal ones) (the chosen one; the rest are alternate keys)
none here; (emp_id, project_id) in employee_projects
Needs both columns to be unique
Foreign key
dept_id, manager_id
Refer to keys of other rows
emp_name alone isn't a key: two employees can share a name. (emp_id, emp_name) is a super key, but not a candidate key, because emp_name is unnecessary.
Follow-ups
Why choose emp_id over email? It's short, never changes and isn't personal data. Emails change, and every foreign key pointing at them would have to change too.
What is a surrogate key? A key with no business meaning, generated by the database, such as AUTO_INCREMENT or a UUID.
Write the CREATE TABLE statement for employees with all of its keys and constraints
Say this first~30 sec to say
Declare each column with its type and NOT NULL where required, make emp_id an AUTO_INCREMENTprimary key, put UNIQUE on email, a CHECK (salary > 0) constraint, and two foreign keys: dept_id referencing departments, and manager_id referencing employees itself. Naming constraints, such as CONSTRAINT fk_emp_dept, makes error messages and later changes clearer. In MySQL, CHECK is only enforced from version 8.0.16; earlier versions parse it and silently ignore it.
Could you say it?
Full answerHide full answer2 min read
SQL
CREATETABLEemployees (
emp_id INTAUTO_INCREMENT,
emp_name VARCHAR(100) NOTNULL,
email VARCHAR(100) NOTNULL,
salary DECIMAL(10,2) NOTNULL,
hire_date DATENOTNULL,
dept_id INTNULL, -- an employee may have no department yet
manager_id INTNULL, -- the top boss has no managerCONSTRAINT pk_employees PRIMARYKEY (emp_id),
CONSTRAINT uq_employees_email UNIQUE (email),
CONSTRAINT chk_employees_salary CHECK (salary > 0),
CONSTRAINT fk_emp_dept FOREIGNKEY (dept_id) REFERENCESdepartments (dept_id)
ONDELETESETNULL,
CONSTRAINT fk_emp_manager FOREIGNKEY (manager_id) REFERENCESemployees (emp_id)
);
What each constraint stops
Constraint
Rejects
NOT NULL
A missing name, email, salary or hire date
PRIMARY KEY
Duplicate or NULL IDs
UNIQUE (email)
Two employees with one email address
CHECK (salary > 0)
A zero or negative salary
FOREIGN KEY (dept_id)
A department that doesn't exist
FOREIGN KEY (manager_id)
A manager who isn't an employee
DEFAULT (not used here)
Supplies a value when an insert leaves the column out
Why DECIMAL for money?DECIMAL(10,2) stores exact values. FLOAT and DOUBLE are binary approximations, so sums of rupees and paise can drift by tiny amounts.
Follow-ups
How do you add a constraint later?ALTER TABLE employees ADD CONSTRAINT chk_hire CHECK (hire_date >= '2000-01-01');
Can a CHECK constraint refer to another table? No. Cross-table rules need a foreign key, a trigger (question 40) or application code.
03Core SQL
0/5 got it
Q08Not marked yet·Fresher·Asked very often·Concept
DELETE vs TRUNCATE vs DROP: what does each remove, and which can be rolled back?
Say this first~30 sec to say
DELETE is DML: it removes rows one by one, all of them or only those matching a WHERE, fires DELETE triggers, and can be rolled back inside a transaction. TRUNCATE is DDL: it empties the whole table at once, much faster, with no WHERE, and resets AUTO_INCREMENT. DROP removes the table itself: structure, data, indexes and constraints. In MySQL, TRUNCATE and DROP commit implicitly, so neither can be rolled back.
Could you say it?
Full answerHide full answer2 min read
DELETE
TRUNCATE
DROP
Removes
Some or all rows
All rows
The whole table
WHERE allowed
Yes
No
No
Command type
DML
DDL
DDL
Speed on big tables
Slow: row by row, logged
Fast
Fast
Fires DELETE triggers
Yes
No
No
Resets AUTO_INCREMENT
No
Yes
The table is gone
Rollback in MySQL
Yes, inside a transaction
No: implicit commit
No: implicit commit
Blocked by foreign keys pointing at it
Only for referenced rows
Yes, if another table references it
Yes, if another table references it
SQL
STARTTRANSACTION;
DELETEFROM orders WHERE status = 'cancelled'; -- removes 103 and 109ROLLBACK; -- they're backTRUNCATETABLE orders; -- every order gone, and no undo in MySQLDROPTABLEIFEXISTS orders_archive; -- the table itself is removed
Database differences: PostgreSQL and SQL Server let you roll back TRUNCATE, and even DROP, inside a transaction. That's a common follow-up for anyone who says "TRUNCATE can never be rolled back".
Follow-ups
Why is TRUNCATE faster? It deallocates the table's storage pages instead of deleting and logging each row.
How do you remove all rows but keep the IDs counting up?DELETE FROM table without a WHERE: slower, but AUTO_INCREMENT continues from where it was.
Q09Not marked yet·Fresher·Asked very often·Concept
WHERE vs HAVING: when does each filter, and why can't WHERE use an aggregate or a SELECT alias?
Say this first~30 sec to say
WHERE filters rows before grouping; HAVING filters groups afterGROUP BY has formed them. A query runs in a logical order, FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT, which explains both rules. When WHERE runs, there are no groups yet, so AVG(salary) doesn't exist; and the SELECT list hasn't been evaluated, so its aliases don't exist either. Filter rows in WHERE whenever you can: it shrinks the data before the expensive grouping.
Could you say it?
Full answerHide full answer2 min read
Departments whose staff hired since 2018 average over ₹1 lakh
SQL
SELECT dept_id, AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2018-01-01'-- row filter: before groupingGROUPBY dept_id
HAVINGAVG(salary) > 100000; -- group filter: after grouping
dept_id
avg_salary
1
120000.000000
2
135000.000000
HR (Meera and Sameer) averages 87,500, so HAVING drops it; Suresh's group (no department) averages 70,000. MySQL shows AVG of a DECIMAL(10,2) column with six decimal places.
The logical order a query runs in
Step
Clause
What exists at this point
1
FROM, JOIN
All the joined rows
2
WHERE
Rows filtered one at a time
3
GROUP BY
Groups
4
HAVING
Groups filtered
5
SELECT
Output columns and aliases are computed
6
ORDER BY
Aliases can be used here
7
LIMIT / OFFSET
The final cut
SQL
SELECT emp_name, salary * 12AS annual FROM employees WHERE annual > 1500000;
-- ERROR 1054 (42S22): Unknown column 'annual' in 'where clause'
Database differences: MySQL lets you use a SELECT alias in GROUP BY and HAVING, as an extension. PostgreSQL accepts aliases in GROUP BY but not in HAVING, and SQL Server accepts them in neither, so repeating the expression is the portable habit.
Follow-ups
Can HAVING be used without GROUP BY? Yes: the whole result is then one group. SELECT COUNT(*) FROM orders HAVING COUNT(*) > 5 returns one row or none.
Is the logical order the order the database executes in? Not necessarily. The optimiser can reorder work, but the result must be as if it ran in this order.
Q10Not marked yet·Fresher·Asked very often·Concept
UNION vs UNION ALL: what is the difference, and which is faster?
Say this first~25 sec to say
Both stack the results of two SELECTs vertically. UNION removes duplicate rows, which costs extra work, usually a sort or hash over the whole result. UNION ALL keeps every row, so it's faster, and it's the right choice whenever duplicates are impossible or wanted. Both need the same number of columns in each SELECT, with compatible types, and the column names come from the first SELECT.
Could you say it?
Full answerHide full answer1 min read
Every city the company touches, from customers and from department locations
SQL
SELECT city FROM customers
UNIONSELECT location FROM departments
ORDERBY city;
city
Ahmedabad
Bengaluru
Delhi
Hyderabad
Kochi
Mumbai
Pune
UNION returns 7 distinct cities; UNION ALL returns all 10 values, with Delhi three times and Pune twice. The ORDER BY applies to the combined result, and uses the first query's column name.
UNION
UNION ALL
Duplicates
Removed
Kept
Speed
Slower: must find duplicates
Faster
Use when
You need a distinct list
Rows can't overlap, or counts matter
Related set operators:INTERSECT (rows in both) and EXCEPT (rows in the first but not the second). MySQL only supports them from 8.0.31; before that, use JOIN or NOT EXISTS instead.
Follow-ups
UNION vs JOIN? UNION stacks rows from similar queries; JOIN combines columns from related tables side by side.
Does UNION ALL guarantee the order of rows? No. Without ORDER BY, no SQL result has a guaranteed order.
List the five most recently hired employees whose names start with 'S'
Say this first~25 sec to say
Filter with LIKE 'S%', where % matches any run of characters, sort by hire_datedescending so the newest come first, and cut with LIMIT 5. Under MySQL's default collation LIKE is case-insensitive, so 's%' matches too; in PostgreSQL it's case-sensitive and ILIKE is the case-insensitive version. SQL Server has no LIMIT: it uses TOP 5 or OFFSET … FETCH.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT emp_name, hire_date
FROM employees
WHERE emp_name LIKE'S%'ORDERBY hire_date DESCLIMIT5;
emp_name
hire_date
Sameer Joshi
2024-09-01
Suresh Pillai
2024-01-15
Sana Khan
2022-02-14
Sneha Iyer
2018-08-20
Only four employees match, so LIMIT 5 returns four: it's a maximum, not a guarantee.
LIKE patterns
Pattern
Matches
'S%'
Starts with S
'%a'
Ends with a
'%kumar%'
Contains "kumar" anywhere
'_a%'
Second letter is a (_ is exactly one character)
'S____'
S followed by exactly four characters
The same in other databases
SQL
-- SQL ServerSELECT TOP 5 emp_name, hire_date FROM employees WHERE emp_name LIKE'S%'ORDERBY hire_date DESC;
-- Standard SQL (PostgreSQL, Oracle 12c+, SQL Server)SELECT emp_name, hire_date FROM employees WHERE emp_name LIKE'S%'ORDERBY hire_date DESCFETCHFIRST5ROWSONLY;
Follow-ups
Can LIKE 'S%' use an index? Yes: a fixed prefix lets the database range-scan an index on emp_name. A leading wildcard, '%S', can't (question 37).
What if two people were hired the same day? The order between them is undefined; add a tie-breaker such as ORDER BY hire_date DESC, emp_id.
Q12Not marked yet·Fresher·Asked often·Predict the output
On employees, what do WHERE manager_id = NULL, WHERE manager_id != 3 and WHERE emp_id NOT IN (SELECT manager_id FROM employees) each return, and why?
Say this first~30 sec to say
= NULL returns no rows: any comparison with NULL gives UNKNOWN, which WHERE treats as false; use IS NULL. != 3 returns 8 rows, not 9: it skips Arjun, whose manager is 3, and also Asha, whose manager_id is NULL, because NULL != 3 is UNKNOWN too. The NOT IN query returns no rows at all: the subquery's list contains a NULL, and x NOT IN (…, NULL) can never be true. NOT EXISTS avoids that trap.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT emp_name FROM employees WHERE manager_id = NULL;
-- (no rows)SELECT emp_name FROM employees WHERE manager_id ISNULL;
-- Asha RaoSELECTCOUNT(*) FROM employees WHERE manager_id != 3;
-- 8: Arjun (manager 3) and Asha (manager NULL) are both excludedSELECT emp_name FROM employees WHERE emp_id NOTIN (SELECT manager_id FROM employees);
-- (no rows): the list includes Asha's NULLSELECT e.emp_name
FROM employees e
WHERENOTEXISTS (SELECT1FROM employees r WHERE r.manager_id = e.emp_id);
-- Arjun Mehta, Vikram Singh, Sana Khan, Suresh Pillai, Sameer Joshi: the people who manage nobody
Three-valued logic in one table
Expression
Result
NULL = NULL
UNKNOWN
NULL != 3
UNKNOWN
5 NOT IN (1, 2, NULL)
UNKNOWN, because 5 != NULL is unknown
NULL IS NULL
TRUE
UNKNOWN AND FALSE
FALSE
UNKNOWN OR TRUE
TRUE
WHERE keeps only rows where the condition is TRUE, so UNKNOWN rows silently disappear.
Including the NULLs on purpose
SQL
SELECTCOUNT(*) FROM employees WHERE manager_id != 3OR manager_id ISNULL; -- 9
NULL vs 0 vs an empty string: NULL means "unknown or not applicable"; 0 and '' are real values. Replace NULLs for display with COALESCE(manager_id, 0) (standard) or MySQL's IFNULL. Beware: ISNULL(x)tests for NULL in MySQL (returning 1 or 0) but replaces NULL in SQL Server.
Follow-ups
Is NOT IN ever safe? Yes, when the subquery's column can't contain NULL, for example a NOT NULL column, or with WHERE manager_id IS NOT NULL added inside the subquery.
How do aggregates treat NULL? They skip it: COUNT(manager_id) is 9, not 10 (question 19).
04Joins
0/5 got it
Q13Not marked yet·Fresher·Asked very often·Concept
What does each join type return: INNER, LEFT, RIGHT, FULL OUTER and CROSS?
Say this first~30 sec to say
INNER JOIN returns only rows that match in both tables. LEFT JOIN returns every row from the left table, with NULLs on the right where nothing matches; RIGHT JOIN is the mirror image. FULL OUTER JOIN returns every row from both sides, matched where possible and NULL-padded where not. CROSS JOIN pairs every row with every row: m × n rows. MySQL has no FULL OUTER JOIN; you build it from a LEFT JOIN and a RIGHT JOIN.
Could you say it?
Full answerHide full answer2 min read
Employees and departments, joined on dept_id
Join
Rows
What's included
INNER JOIN
9
Employees with a department; Suresh and Finance drop out
employees LEFT JOIN departments
10
Every employee; Suresh gets NULL for the department
employees RIGHT JOIN departments
10
Every department; Finance appears with NULL for the employee
FULL OUTER JOIN
11
Both: Suresh with no department, and Finance with no employee
CROSS JOIN
40
10 employees × 4 departments
SQL
SELECT e.emp_name, d.dept_name
FROM employees e
LEFTJOIN departments d ON d.dept_id = e.dept_id; -- every employee, Suresh with dept_name NULL
FULL OUTER JOIN in MySQL
SQL
SELECT e.emp_name, d.dept_name
FROM employees e LEFTJOIN departments d ON d.dept_id = e.dept_id
UNIONALLSELECT e.emp_name, d.dept_name
FROM employees e RIGHTJOIN departments d ON d.dept_id = e.dept_id
WHERE e.emp_id ISNULL; -- only the right-side rows with no match
That returns the same 11 rows a real FULL OUTER JOIN would. Many tutorials use UNION instead of UNION ALL without the WHERE, which works only when the rows contain no duplicates (question 16 shows how it breaks).
INNER: [ A ∩ B ] LEFT: [ A ] + matches RIGHT: matches + [ B ]
FULL: [ A ∪ B ] CROSS: every A paired with every B
Follow-ups
What is a self join? A table joined to itself, with two aliases, such as employees and their managers (question 14).
What is a NATURAL JOIN? A join on every column with the same name in both tables. Avoid it: adding a column later can silently change the join.
Does the order of tables matter? For INNER and CROSS joins, no; for LEFT and RIGHT, yes: it decides which side keeps all its rows.
List employees who are paid more than their own manager, showing both names and salaries
Say this first~25 sec to say
Use a self join: join employees to itself, one copy as the employee (e) and one as the manager (m), on e.manager_id = m.emp_id, then keep rows where e.salary > m.salary. An INNER JOIN is right here, because the top boss has no manager to compare with. The usual warm-up, listing everyone with their manager's name, needs a LEFT self join so the top boss isn't dropped.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT e.emp_name, e.salary,
m.emp_name AS manager_name, m.salary AS manager_salary
FROM employees e
JOIN employees m ON e.manager_id = m.emp_id -- m is the manager's rowWHERE e.salary > m.salary;
emp_name
salary
manager_name
manager_salary
Sana Khan
160000.00
Sneha Iyer
150000.00
Sameer Joshi
90000.00
Meera Nair
85000.00
Arjun earns exactly what his manager Priya earns, so the strict > leaves him out.
The warm-up: everyone with their manager
SQL
SELECT e.emp_name, m.emp_name AS manager
FROM employees e
LEFTJOIN employees m ON e.manager_id = m.emp_id
ORDERBY e.emp_id;
emp_name
manager
Asha Rao
NULL
Ravi Kumar
Asha Rao
Priya Shah
Ravi Kumar
Arjun Mehta
Priya Shah
Sneha Iyer
Asha Rao
…
…
With an INNER JOIN, Asha would vanish, because she has no manager row to match.
What is a self join? Not a special keyword: an ordinary join where both sides are the same table, distinguished by aliases. It's how you query any hierarchy stored in one table: employees and managers, categories and parent categories, replies and the comments they reply to.
Follow-ups
How would you count each manager's direct reports?SELECT m.emp_name, COUNT(*) FROM employees e JOIN employees m ON e.manager_id = m.emp_id GROUP BY m.emp_id, m.emp_name;
How do you get everyone below a manager, at every level? A recursive CTE (question 26).
List the customers who signed up but have never ordered
Say this first~25 sec to say
This is an anti-join: rows in one table with no match in another. Either LEFT JOIN orders and keep the rows where the order side IS NULL, or use NOT EXISTS with a correlated subquery. Both return Fatima and Deepak. Avoid NOT IN with a subquery on a nullable column: if that column contains a single NULL, NOT IN returns nothing at all (question 12).
Could you say it?
Full answerHide full answer2 min read
SQL
-- LEFT JOIN ... IS NULLSELECT c.customer_id, c.customer_name
FROM customers c
LEFTJOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id ISNULL;
-- NOT EXISTS: often the clearest, and NULL-safeSELECT c.customer_id, c.customer_name
FROM customers c
WHERENOTEXISTS (SELECT1FROM orders o WHERE o.customer_id = c.customer_id);
customer_id
customer_name
5
Fatima Sheikh
6
Deepak Rao
Why check o.order_id, a primary key, for NULL? A primary key is never NULL in a real row, so a NULL there can only mean "no matching row". Checking a nullable column such as o.emp_id would also catch real orders that happen to have no sales rep.
Approach
NULL-safe
Notes
LEFT JOIN … WHERE right.pk IS NULL
Yes
Familiar; shows the anti-join clearly
NOT EXISTS (correlated subquery)
Yes
Stops at the first match; optimisers handle it well
NOT IN (subquery)
No
Wrong as soon as the subquery returns a NULL
Follow-ups
Customers whose last order was over 90 days ago? Group orders by customer, take MAX(order_date), and keep those before CURDATE() - INTERVAL 90 DAY, plus the never-ordered ones from this query.
Which is faster, NOT EXISTS or LEFT JOIN … IS NULL? Usually the same plan in modern optimisers; check with EXPLAIN rather than guessing.
Q16Not marked yet·Fresher·Asked often·Predict the output
Two one-column tables hold (1, 1, 2, NULL) and (1, 1, 3, NULL). How many rows do INNER, LEFT, RIGHT, FULL OUTER and CROSS JOIN each return when joined on that column?
Say this first~30 sec to say
INNER: 4. Each of the two 1s on the left matches both 1s on the right, 2 × 2 = 4, and NULL never equals NULL, so it doesn't match. LEFT: 6: the 4 matches plus the unmatched 2 and NULL from the left. RIGHT: 6: the 4 matches plus 3 and NULL from the right. FULL OUTER: 8: the 4 matches plus both sides' unmatched rows. CROSS: 16: 4 × 4, every pairing. Duplicates multiply matches; NULLs never join.
Could you say it?
Full answerHide full answer2 min read
SQL
CREATETABLEa (v INT);
CREATETABLEb (v INT);
INSERTINTO a VALUES (1), (1), (2), (NULL);
INSERTINTO b VALUES (1), (1), (3), (NULL);
SELECTCOUNT(*) FROM a JOIN b ON a.v = b.v; -- 4SELECTCOUNT(*) FROM a LEFTJOIN b ON a.v = b.v; -- 6SELECTCOUNT(*) FROM a RIGHTJOIN b ON a.v = b.v; -- 6SELECTCOUNT(*) FROM a CROSSJOIN b; -- 16
Join
Rows
Made of
INNER
4
(1,1) × 4
LEFT
6
4 matches + (2, NULL) + (NULL, NULL)
RIGHT
6
4 matches + (NULL, 3) + (NULL, NULL)
FULL OUTER
8
4 matches + 2 left-only + 2 right-only
CROSS
16
4 × 4
The MySQL FULL JOIN trap. The common workaround, LEFT JOIN … UNION … RIGHT JOIN, returns only 4 rows here, not 8. UNION removes duplicates, so the four identical (1, 1) rows collapse into one, and the two (NULL, NULL) rows into one. The safe version uses UNION ALL and keeps only the unmatched rows from the second half (question 13).
Follow-ups
How would you join NULLs to NULLs on purpose? MySQL's null-safe equality operator <=> treats two NULLs as equal; the standard spelling is IS NOT DISTINCT FROM.
Why does this matter in practice? Joining on a non-unique column multiplies rows, which silently inflates sums and counts. Check row counts before and after a join.
Q17Not marked yet·1–3 yrs·Asked sometimes·Predict the output
In customers LEFT JOIN orders, what changes when the condition status = 'delivered' moves from the ON clause to the WHERE clause?
Say this first~30 sec to say
In the ON clause, the condition only decides which orders match: every customer still appears, and customers with no delivered orders get NULLs. In the WHERE clause, it filters after the join: the NULL-extended rows have status NULL, which fails the test, so they're dropped, and the LEFT JOIN behaves like an inner join. Here, ON gives 11 rows covering all 6 customers; WHERE gives 8 rows covering only the 3 with delivered orders.
Could you say it?
Full answerHide full answer2 min read
SQL
-- Condition in ON: still a real LEFT JOINSELECT c.customer_name, o.order_id
FROM customers c
LEFTJOIN orders o ON o.customer_id = c.customer_id AND o.status = 'delivered';
-- 11 rows: Rahul V, Fatima and Deepak appear with order_id NULL-- Condition in WHERE: the LEFT JOIN collapses into an inner joinSELECT c.customer_name, o.order_id
FROM customers c
LEFTJOIN orders o ON o.customer_id = c.customer_id
WHERE o.status = 'delivered';
-- 8 rows: only Kiran, Lakshmi and Rahul Verma
Customer
Condition in ON
Condition in WHERE
Kiran Patel
101, 102
101, 102
Lakshmi Menon
107, 108, 111
107, 108, 111
Rahul Verma
104, 105, 110
104, 105, 110
Rahul V (only a placed order)
NULL
(gone)
Fatima Sheikh (no orders)
NULL
(gone)
Deepak Rao (no orders)
NULL
(gone)
The rule: for an outer join, conditions on the optional (right-hand) table belong in ON if you want to keep the unmatched rows. Conditions on the preserved (left) table, such as c.city = 'Delhi', belong in WHERE.
Follow-ups
Does it matter for an INNER JOIN? No: for inner joins, ON and WHERE conditions give the same result.
How would you count delivered orders per customer, zeros included? Put the status test in ON and count o.order_id: COUNT(o.order_id) gives 0 for customers with no match.
05Aggregation and grouping
0/6 got it
Q18Not marked yet·Fresher·Asked very often·Concept
What does GROUP BY do, and why does selecting a column that is neither grouped nor aggregated fail?
Say this first~30 sec to say
GROUP BY collapses rows that share the same values in the grouping columns into one output row per group, and aggregate functions such as COUNT, SUM, AVG, MIN and MAX summarise each group. Every column in the SELECT list must therefore be either grouped or aggregated: a plain column like emp_name has many values in a group, so the database can't know which one to show. MySQL 8 rejects it under ONLY_FULL_GROUP_BY, which is on by default.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT dept_id, COUNT(*) AS staff, MAX(salary) AS top_salary
FROM employees
GROUPBY dept_id;
dept_id
staff
top_salary
NULL
1
70000.00
1
4
250000.00
2
3
160000.00
3
2
90000.00
NULL forms its own group: Suresh, who has no department.
The error
SQL
SELECT dept_id, emp_name, MAX(salary)
FROM employees
GROUPBY dept_id;
-- ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains-- nonaggregated column 'company.employees.emp_name' which is not functionally dependent on-- columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
Older MySQL versions (before 5.7) accepted this and returned an arbitrary emp_name from each group, often not the person with the top salary, which is why some old tutorials show it "working".
The fix depends on what you meant: to show the top earner's name, join back to the maximum or use a window function (questions 21 and 29); to show any one name, say so explicitly with ANY_VALUE(emp_name).
Follow-ups
Can you group by several columns? Yes: GROUP BY dept_id, YEAR(hire_date) gives one row per department per hiring year.
What does "functionally dependent" mean in that error? If you group by a primary key, every other column of that row has one value per group, so MySQL allows selecting them.
Q19Not marked yet·Fresher·Asked often·Predict the output
On employees, what do COUNT(*), COUNT(manager_id) and COUNT(DISTINCT dept_id) return, and why do they differ?
Say this first~20 sec to say
COUNT(*) returns 10: it counts rows, whatever they contain. COUNT(manager_id) returns 9: counting a column skips NULLs, and Asha has no manager. COUNT(DISTINCT dept_id) returns 3: it skips the NULL and counts each department only once. The same NULL rule applies to SUM, AVG, MIN and MAX: they ignore NULLs instead of treating them as 0.
Could you say it?
Full answerHide full answer1 min read
SQL
SELECTCOUNT(*), COUNT(manager_id), COUNT(DISTINCT dept_id)
FROM employees;
COUNT(*)
COUNT(manager_id)
COUNT(DISTINCT dept_id)
10
9
3
Expression
Counts
Here
COUNT(*)
Rows
10
COUNT(1)
Rows: identical to COUNT(*), not faster
10
COUNT(column)
Non-NULL values
9 for manager_id
COUNT(DISTINCT column)
Distinct non-NULL values
3 for dept_id
AVG skips NULLs too, which changes the answer
If a bonus column held 100, 200 and NULL, AVG(bonus) is 150 (300 ÷ 2), not 100 (300 ÷ 3). If NULL should count as zero, say so: AVG(COALESCE(bonus, 0)).
Follow-ups
Why does COUNT(*) give 1 for an empty department in a LEFT JOIN? Because the NULL-extended row is still a row; count a column from the right-hand table instead (question 22).
What does SUM return for a group with only NULLs, or no rows? NULL, not 0; wrap it in COALESCE(SUM(x), 0) when you need a number.
Find email addresses used by more than one customer, with how many times each appears
Say this first~25 sec to say
Group by email and keep only the groups with more than one row: GROUP BY email HAVING COUNT(*) > 1. The HAVING is essential, because the condition is on a count, which only exists after grouping. Here it returns [email protected], used twice. To see the full duplicate rows, join back to this result, or use a window function.
Could you say it?
Full answerHide full answer1 min read
SQL
SELECT email, COUNT(*) AS times
FROM customers
GROUPBY email
HAVINGCOUNT(*) > 1;
Watch out for: case and spaces. [email protected] and [email protected] are different strings; group by LOWER(TRIM(email)) to catch them. (MySQL 8's default collation already ignores case, but it treats trailing spaces as significant, so normalising is the portable habit.)
Follow-ups
How would you delete the duplicates? Keep one row per email and delete the rest, as question 27 does for orders. For customers, merge their orders first.
Duplicates across several columns? Group by all of them: GROUP BY customer_id, order_date, amount HAVING COUNT(*) > 1.
Show each department's name next to the highest salary paid in it
Say this first~25 sec to say
Join employees to departments to get the names, GROUP BY the department, and take MAX(salary) in each group. Group by the department's ID as well as its name, so two departments that happen to share a name can't be merged. An inner join leaves out departments with no employees and employees with no department; switch to a LEFT JOIN from departments if empty departments should appear.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT d.dept_name, MAX(e.salary) AS highest_salary
FROM employees e
JOIN departments d ON d.dept_id = e.dept_id
GROUPBY d.dept_id, d.dept_name
ORDERBY highest_salary DESC;
dept_name
highest_salary
Engineering
250000.00
Sales
160000.00
HR
90000.00
The follow-up: who earns it?
Adding e.emp_name to the query above fails (question 18). Join back to the per-department maximum instead:
SQL
SELECT d.dept_name, e.emp_name, e.salary
FROM employees e
JOIN departments d ON d.dept_id = e.dept_id
JOIN (
SELECT dept_id, MAX(salary) AS max_salary
FROM employees
GROUPBY dept_id
) top ON top.dept_id = e.dept_id AND top.max_salary = e.salary;
dept_name
emp_name
salary
Engineering
Asha Rao
250000.00
Sales
Sana Khan
160000.00
HR
Sameer Joshi
90000.00
If two people tie for the top salary, both appear, which is usually what's wanted. The window-function version is in question 29.
Follow-ups
Highest salary per department, but only departments with at least three people? Add HAVING COUNT(*) >= 3.
And the lowest, average and total, too? Add MIN(e.salary), ROUND(AVG(e.salary), 2) and SUM(e.salary) to the same SELECT.
Show every department with its number of employees, including departments that have none
Say this first~20 sec to say
Start from departments and LEFT JOIN employees, so departments without staff still appear, then GROUP BY the department and count e.emp_id, not *. COUNT(e.emp_id) skips the NULL produced for an empty department and returns 0, whereas COUNT(*) counts that NULL-padded row and wrongly reports 1. Here Finance has no employees and must show 0.
Could you say it?
Full answerHide full answer1 min read
SQL
SELECT d.dept_name, COUNT(e.emp_id) AS employees
FROM departments d
LEFTJOIN employees e ON e.dept_id = d.dept_id
GROUPBY d.dept_id, d.dept_name
ORDERBY employees DESC;
dept_name
employees
Engineering
4
Sales
3
HR
2
Finance
0
With COUNT(*), Finance would show 1: the LEFT JOIN creates one row for it, with every employee column NULL, and COUNT(*) counts rows.
The common variant: departments with more than three people
SQL
SELECT d.dept_name, COUNT(e.emp_id) AS employees
FROM departments d
LEFTJOIN employees e ON e.dept_id = d.dept_id
GROUPBY d.dept_id, d.dept_name
HAVINGCOUNT(e.emp_id) > 3; -- Engineering, with 4
Follow-ups
What about the employee with no department? This query starts from departments, so Suresh doesn't appear. Counting "unassigned" staff needs a separate query, or a FULL OUTER JOIN.
How is this the same as the pandas question? It's the SQL form of a left merge followed by counting non-null values (ML kit, question 28).
For each customer, show the number of delivered and cancelled orders as two columns in one row
Say this first~25 sec to say
Use conditional aggregation: group by customer, and inside the aggregate, count only the rows that match each status. SUM(CASE WHEN o.status = 'delivered' THEN 1 ELSE 0 END) counts delivered orders, and the same with 'cancelled' gives the second column. LEFT JOIN from customers so those without orders show zeros. This pivots row values into columns in a single pass over the data.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT c.customer_name,
SUM(CASEWHEN o.status = 'delivered'THEN1ELSE0END) AS delivered,
SUM(CASEWHEN o.status = 'cancelled'THEN1ELSE0END) AS cancelled
FROM customers c
LEFTJOIN orders o ON o.customer_id = c.customer_id
GROUPBY c.customer_id, c.customer_name
ORDERBY c.customer_id;
customer_name
delivered
cancelled
Kiran Patel
2
1
Lakshmi Menon
3
1
Rahul Verma
3
0
Rahul V
0
0
Fatima Sheikh
0
0
Deepak Rao
0
0
For customers without orders, o.status is NULL, so every CASE falls through to ELSE 0.
What CASE does, the usual fresher warm-up: it returns a value based on conditions, like if-else inside a query:
SQL
SELECT emp_name, salary,
CASEWHEN salary >= 150000THEN'High'WHEN salary >= 90000THEN'Medium'ELSE'Low'ENDAS band
FROM employees;
The conditions are checked top to bottom and the first match wins, so order them from the most specific.
A MySQL shortcut that doesn't travel: in MySQL, SUM(o.status = 'delivered') works, because a comparison evaluates to 1 or 0. PostgreSQL and SQL Server reject it. COUNT(CASE WHEN o.status = 'delivered' THEN 1 END) is another portable form, since COUNT skips the NULLs from unmatched CASEs.
Follow-ups
How would you add each customer's delivered revenue?SUM(CASE WHEN o.status = 'delivered' THEN o.amount ELSE 0 END).
What if the statuses aren't known in advance? Static SQL needs fixed columns. Dynamic pivots need prepared statements, or are better done in the reporting tool.
Find the second-highest distinct salary in the company
Say this first~30 sec to say
Two classic answers. Take the maximum of the salaries below the maximum: SELECT MAX(salary) FROM employees WHERE salary < (SELECT MAX(salary) FROM employees). Or list the distinct salaries in descending order and skip the first: ORDER BY salary DESC LIMIT 1 OFFSET 1. Both return 180,000 here. Say how you handle ties and edge cases: with only one distinct salary, the first returns NULL and the second returns no rows. For the Nth highest, DENSE_RANK() generalises best.
Could you say it?
Full answerHide full answer2 min read
SQL
-- 1. The max below the maxSELECTMAX(salary) AS second_highest
FROM employees
WHERE salary < (SELECTMAX(salary) FROM employees);
-- 2. Distinct salaries, skip the firstSELECTDISTINCT salary AS second_highest
FROM employees
ORDERBY salary DESCLIMIT1OFFSET1;
second_highest
180000.00
Why DISTINCT matters: without it, a tie for the top salary would make the "second" row repeat the highest value. That's also why the first method works: salary < MAX naturally skips every copy of the top salary.
The Nth highest: DENSE_RANK
SQL
SELECTDISTINCT salary
FROM (
SELECT salary, DENSE_RANK() OVER (ORDERBY salary DESC) AS salary_rank
FROM employees
) ranked
WHERE salary_rank = 5; -- 120000.00: shared by Priya and Arjun
DENSE_RANK gives tied salaries the same rank and doesn't skip numbers afterwards, so "the 5th highest salary" means the 5th distinct value (question 28).
Method
Edge case: only one salary
Generalises to N?
MAX(...) WHERE salary < MAX
Returns NULL
Awkwardly: nested N levels
DISTINCT … LIMIT 1 OFFSET 1
No rows
Yes: OFFSET N-1
DENSE_RANK()
No rows
Yes, and per group with PARTITION BY
Follow-ups
The second-highest salary in each department?DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC), then filter rank 2 (question 29).
Why not LIMIT 1, 1? It means the same as LIMIT 1 OFFSET 1 in MySQL, but the explicit OFFSET form is clearer and more portable.
List employees who earn more than the average salary of their own department
Say this first~30 sec to say
Use a correlated subquery: for each employee row, a subquery computes the average salary of that employee's department, WHERE e2.dept_id = e.dept_id, and the outer query keeps the row if the salary is higher. It's "correlated" because the inner query refers to the outer row, so it conceptually runs once per row. The equivalent join computes all department averages once, in a derived table, then joins: often faster on big tables.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT e.emp_name, e.dept_id, e.salary
FROM employees e
WHERE e.salary > (
SELECTAVG(e2.salary)
FROM employees e2
WHERE e2.dept_id = e.dept_id -- refers to the outer row: correlated
)
ORDERBY e.dept_id, e.salary DESC;
emp_name
dept_id
salary
Asha Rao
1
250000.00
Ravi Kumar
1
180000.00
Sana Khan
2
160000.00
Sneha Iyer
2
150000.00
Sameer Joshi
3
90000.00
The department averages are 167,500 (Engineering), 135,000 (Sales) and 87,500 (HR). Suresh has no department: e2.dept_id = NULL matches nothing, the average is NULL, and the comparison drops him.
The same with a join to the averages
SQL
SELECT e.emp_name, e.dept_id, e.salary, ROUND(a.avg_salary) AS dept_avg
FROM employees e
JOIN (
SELECT dept_id, AVG(salary) AS avg_salary
FROM employees
GROUPBY dept_id
) a ON a.dept_id = e.dept_id
WHERE e.salary > a.avg_salary;
The warm-up: above the company average, a plain (non-correlated) subquery that runs once:
SQL
SELECT emp_name, salary
FROM employees
WHERE salary > (SELECTAVG(salary) FROM employees); -- average 132000.00: Asha, Ravi, Sana, Sneha
Follow-ups
What's a correlated subquery, in one line? A subquery that depends on the current row of the outer query, so its result changes row by row.
And the window-function version?AVG(salary) OVER (PARTITION BY dept_id) puts each department's average on every row without grouping, then an outer query filters.
What is a CTE, and when would you use one instead of a subquery or a view?
Say this first~30 sec to say
A CTE (common table expression) is a named, temporary result defined with WITH name AS (…) at the start of a statement and usable only inside it. It splits a multi-step query into readable, named steps, and can be referenced more than once. Use one where nested subqueries get hard to read; use a view when a query should be saved and reused. CTEs need MySQL 8.0+, and WITH RECURSIVE walks hierarchies.
Could you say it?
Full answerHide full answer3 min read
SQL
WITH dept_stats AS (
SELECT dept_id, AVG(salary) AS avg_salary, COUNT(*) AS staff
FROM employees
WHERE dept_id ISNOTNULLGROUPBY dept_id
),
big_depts AS (
SELECT dept_id, avg_salary FROM dept_stats WHERE staff >= 3
)
SELECT d.dept_name, ROUND(b.avg_salary) AS avg_salary
FROM big_depts b
JOIN departments d ON d.dept_id = b.dept_id;
dept_name
avg_salary
Engineering
167500
Sales
135000
Recursive CTEs: everyone in the org chart, with their level
SQL
WITHRECURSIVE org AS (
SELECT emp_id, emp_name, manager_id, 1ASlevelFROM employees
WHERE manager_id ISNULL-- the anchor: the top bossUNIONALLSELECT e.emp_id, e.emp_name, e.manager_id, o.level + 1FROM employees e
JOIN org o ON e.manager_id = o.emp_id -- the recursive step: reports of the previous level
)
SELECT emp_name, levelFROM org ORDERBYlevel, emp_id;
emp_name
level
Asha Rao
1
Ravi Kumar
2
Sneha Iyer
2
Meera Nair
2
Suresh Pillai
2
Priya Shah
3
Vikram Singh
3
Sana Khan
3
Sameer Joshi
3
Arjun Mehta
4
Change the anchor to WHERE emp_id = 5 and the query returns Sneha and everyone under her: Vikram and Sana.
CTE
Subquery
View
Lives
For one statement
Inline, for one statement
In the database, until dropped
Named
Yes
Only if a derived table gets an alias
Yes
Reusable within the query
Yes, by name
No: repeat it
Yes
Recursive
Yes, with WITH RECURSIVE
No
No
Best for
Readable multi-step queries, hierarchies
Short, one-off filters
Reused logic, simplifying access, security
Follow-ups
Is a CTE faster than a subquery? Usually the same: MySQL can merge simple CTEs into the main query, much like a derived table. Choose it for readability.
What stops a recursive CTE looping forever? The recursion ends when a step returns no rows. MySQL also stops at cte_max_recursion_depth (1,000 by default), which catches cycles in bad data.
Some orders were saved twice (same customer, date and amount). Delete the extra copies, keeping the lowest order_id
Say this first~30 sec to say
Two good answers. A self-join DELETE: join orders to itself on customer, date and amount where the other copy's order_id is lower, and delete those rows, since each has an earlier twin. Or number each duplicate group with ROW_NUMBER() partitioned by the duplicate columns, ordered by order_id, and delete every row numbered above 1. In MySQL, a DELETE whose subquery reads the same table directly fails with error 1093; the self-join form avoids it.
Could you say it?
Full answerHide full answer2 min read
Before: orders 104 and 105 are one duplicate pair; 107, 108 and 111 are a triple.
SQL
-- 1. Self-join DELETE (MySQL)DELETE later
FROM orders later
JOIN orders earlier
ON earlier.customer_id = later.customer_id
AND earlier.order_date = later.order_date
AND earlier.amount = later.amount
AND earlier.order_id < later.order_id; -- "later" has an earlier twin, so it goesSELECT order_id FROM orders ORDERBY order_id;
-- 101, 102, 103, 104, 106, 107, 109, 110: 105, 108 and 111 are gone
With ROW_NUMBER
SQL
DELETEFROM orders
WHERE order_id IN (
SELECT order_id FROM (
SELECT order_id,
ROW_NUMBER() OVER (PARTITIONBY customer_id, order_date, amount ORDERBY order_id) AS copy_no
FROM orders
) numbered -- the extra derived table is what keeps MySQL happyWHERE copy_no > 1
);
Without the outer SELECT … FROM (…) numbered wrapper, MySQL refuses with ERROR 1093 (HY000): You can't specify target table 'orders' for update in FROM clause. The wrapper makes MySQL materialise the subquery's result first.
Before deleting anything in a real database
Run the SELECT version first and check exactly which rows would go.
Do it inside a transaction, so a mistake can be rolled back.
Check for child rows: delivery records or payments pointing at an order you're about to delete.
Prevent it recurring: a unique constraint, or an idempotency key on the API that creates orders (Backend kit, question 8).
Follow-ups
What if "duplicate" should ignore the amount? Change the columns in the join or the PARTITION BY; that list is the definition of a duplicate, so agree it first.
How would you keep the latest copy instead? Flip the comparison to earlier.order_id > later.order_id, or order the ROW_NUMBER by order_id DESC.
ROW_NUMBER vs RANK vs DENSE_RANK: how does each number tied salaries?
Say this first~30 sec to say
All three number rows in a given order, and unlike GROUP BY, window functions keep every row. ROW_NUMBER gives every row a unique number, so ties are split arbitrarily: 5, 6. RANK gives ties the same rank and then skips: 5, 5, 7. DENSE_RANK gives ties the same rank without a gap: 5, 5, 6. Use ROW_NUMBER to pick exactly one row per group, DENSE_RANK for "Nth highest value" questions, and RANK for competition-style standings. They need MySQL 8.0 or later.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECT emp_name, salary,
ROW_NUMBER() OVER (ORDERBY salary DESC) AS row_num,
RANK() OVER (ORDERBY salary DESC) AS rnk,
DENSE_RANK() OVER (ORDERBY salary DESC) AS dense_rnk
FROM employees;
emp_name
salary
row_num
rnk
dense_rnk
Asha Rao
250000.00
1
1
1
Ravi Kumar
180000.00
2
2
2
Sana Khan
160000.00
3
3
3
Sneha Iyer
150000.00
4
4
4
Priya Shah
120000.00
5
5
5
Arjun Mehta
120000.00
6
5
5
Vikram Singh
95000.00
7
7
6
Sameer Joshi
90000.00
8
8
7
Meera Nair
85000.00
9
9
8
Suresh Pillai
70000.00
10
10
9
Priya and Arjun tie at 120,000. ROW_NUMBER splits them 5 and 6, in an order you shouldn't rely on unless you add a tie-breaker such as ORDER BY salary DESC, emp_id. RANK gives both 5 and then jumps to 7; DENSE_RANK gives both 5 and continues at 6.
Window functions vs GROUP BY
GROUP BY
Window function (OVER)
Output rows
One per group
One per input row
Can show detail columns too
No
Yes
Example
Total salary per department
Each employee next to their department's total
SQL
SELECT emp_name, dept_id, salary,
SUM(salary) OVER (PARTITIONBY dept_id) AS dept_total -- every row keeps its detailFROM employees;
Follow-ups
What does PARTITION BY do? It restarts the numbering, or the calculation, for each group (question 29).
Other window functions worth naming?LAG and LEAD (previous and next row), SUM and AVG over windows (running totals, question 30), NTILE (split rows into buckets), FIRST_VALUE.
List the three best-paid employees in each department, letting tied salaries share a place
Say this first~25 sec to say
Rank employees within each department with DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC), so ties share a place, then keep ranks 1 to 3. The filter must go in an outer query or CTE: window functions are computed after WHERE, so the rank doesn't exist yet when WHERE runs. In Engineering, Priya and Arjun tie for third, so that department returns four people.
Could you say it?
Full answerHide full answer2 min read
SQL
WITH ranked AS (
SELECT d.dept_name, e.emp_name, e.salary,
DENSE_RANK() OVER (PARTITIONBY e.dept_id ORDERBY e.salary DESC) AS pay_rank
FROM employees e
JOIN departments d ON d.dept_id = e.dept_id
)
SELECT dept_name, emp_name, salary, pay_rank
FROM ranked
WHERE pay_rank <= 3-- allowed here: the rank exists in the CTE's outputORDERBY dept_name, pay_rank, emp_name;
dept_name
emp_name
salary
pay_rank
Engineering
Asha Rao
250000.00
1
Engineering
Ravi Kumar
180000.00
2
Engineering
Arjun Mehta
120000.00
3
Engineering
Priya Shah
120000.00
3
HR
Sameer Joshi
90000.00
1
HR
Meera Nair
85000.00
2
Sales
Sana Khan
160000.00
1
Sales
Sneha Iyer
150000.00
2
Sales
Vikram Singh
95000.00
3
HR has only two people, so it returns two. The inner join leaves out Suresh; without it, PARTITION BY dept_id would put him in a NULL "department" of his own, ranked 1.
Choosing the ranking function changes the answer
Function
Engineering returns
Why
DENSE_RANK
4 people
Ties share 3rd place
RANK
4 people
Same here; a tie at 2nd would skip 3rd
ROW_NUMBER
Exactly 3 people
One of Priya and Arjun is cut, arbitrarily
The common variant, "the top earner in each department": the same query with WHERE pay_rank = 1.
Follow-ups
Why not WHERE DENSE_RANK() OVER (...) <= 3 directly?WHERE runs before window functions are computed, so MySQL rejects window functions in WHERE. Some databases add QUALIFY for exactly this; MySQL doesn't have it.
What if the interviewer wants exactly three rows per department? Use ROW_NUMBER with a deterministic tie-breaker, such as ORDER BY salary DESC, hire_date.
Show each month's total order value with a running total alongside it
Say this first~25 sec to say
First group by month to get each month's total, then add a window function over those grouped rows: SUM(SUM(amount)) OVER (ORDER BY month). The inner SUM is the month's total; the outer SUM … OVER accumulates the totals in month order. In MySQL, DATE_FORMAT(order_date, '%Y-%m') makes the month label; PostgreSQL uses DATE_TRUNC or TO_CHAR. Decide with the interviewer whether cancelled orders count; here they're excluded.
Could you say it?
Full answerHide full answer2 min read
SQL
SELECTDATE_FORMAT(order_date, '%Y-%m') AS month,
SUM(amount) AS month_total,
SUM(SUM(amount)) OVER (ORDERBYDATE_FORMAT(order_date, '%Y-%m')) AS running_total
FROM orders
WHERE status <> 'cancelled'GROUPBYDATE_FORMAT(order_date, '%Y-%m')
ORDERBY month;
month
month_total
running_total
2025-01
1650.00
1650.00
2025-02
1798.00
3448.00
2025-03
7000.00
10448.00
2025-04
5600.00
16048.00
(These totals still include the duplicate orders from question 27; delete those first for true revenue.)
The same in two clear steps, with a CTE
SQL
WITH monthly AS (
SELECTDATE_FORMAT(order_date, '%Y-%m') AS month, SUM(amount) AS month_total
FROM orders
WHERE status <> 'cancelled'GROUPBY month
)
SELECT month, month_total,
SUM(month_total) OVER (ORDERBY month) AS running_total,
month_total - LAG(month_total) OVER (ORDERBY month) AS change_vs_last_month
FROM monthly
ORDERBY month;
LAG returns the previous row's value, so change_vs_last_month is NULL for January, then 148.00, 5202.00 and -1400.00.
Frames, briefly: with ORDER BY and no frame, a running SUM uses RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. A moving three-month average spells the frame out: AVG(month_total) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW).
Follow-ups
What if a month has no orders at all? It won't appear. Generate the full list of months, with a calendar table or a recursive CTE, and LEFT JOIN the totals to it.
A running total per customer? Add PARTITION BY customer_id to the window.
08Normalization
0/2 got it
Q31Not marked yet·Fresher·Asked very often·Concept
What is normalization, and which insert, update and delete anomalies does it prevent?
Say this first~25 sec to say
Normalization organises tables so each fact is stored once, splitting data into related tables linked by keys. Copying a fact into many rows causes three anomalies. Update: change one copy but not the others, and the data contradicts itself. Insert: you can't record a fact without an unrelated one. Delete: removing one fact destroys another. Normal forms are the rules for removing that redundancy step by step.
Could you say it?
Full answerHide full answer2 min read
One wide table, three anomalies
emp_id
emp_name
dept_name
dept_location
1
Asha Rao
Engineering
Bengaluru
2
Ravi Kumar
Engineering
Bengaluru
5
Sneha Iyer
Sales
Mumbai
Anomaly
What goes wrong here
Update
Engineering moves to Hyderabad: miss one row, and Engineering is in two cities at once
Insert
A new Finance department can't be recorded until it has an employee, since every row needs one
Delete
Sneha leaves: deleting her row also deletes the only record that Sales is in Mumbai
The normalized version stores the department's facts once, in departments, and each employee just points at it with dept_id. That's exactly the shared schema on this page: Finance can exist with no employees, and moving Engineering is a one-row update.
Denormalization, the usual follow-up: deliberately adding redundancy back for faster reads, such as storing order_total on an order instead of summing its lines every time, or copying the customer's name into a reporting table. You trade write complexity and the risk of drift for fewer joins; it's common in analytics and read-heavy systems.
Follow-ups
Does normalization make queries slower? Reads that need joins can be, slightly; writes are simpler and safer. Normalize first, then denormalize specific spots where measurements show it's needed.
What is redundancy, precisely? The same fact stored in more than one place, which is what lets copies disagree.
Q32Not marked yet·Fresher·Asked very often·Concept
What do 1NF, 2NF, 3NF and BCNF each require? Show it by normalizing one wide table step by step
Say this first~30 sec to say
1NF: every column holds a single, atomic value, with no lists or repeating groups. 2NF: 1NF, and no non-key column depends on only part of a composite key. 3NF: 2NF, and no non-key column depends on another non-key column (no transitive dependency). BCNF: for every functional dependency X → Y, X must be a candidate key. A functional dependency, X → Y, means knowing X fixes Y, as emp_id determines emp_name.
Could you say it?
Full answerHide full answer3 min read
Start: one unnormalized table of employee project assignments
emp_id
emp_name
dept_id
dept_name
projects
1
Asha Rao
1
Engineering
P1 (40 hrs), P2 (10 hrs)
2
Ravi Kumar
1
Engineering
P1 (25 hrs)
1NF: make values atomic.projects holds a list, so split it into one row per employee per project:
emp_id
project_id
emp_name
dept_id
dept_name
hours
1
P1
Asha Rao
1
Engineering
40
1
P2
Asha Rao
1
Engineering
10
2
P1
Ravi Kumar
1
Engineering
25
The key is now (emp_id, project_id).
2NF: remove partial dependencies.emp_name, dept_id and dept_name depend on emp_id alone, which is only part of the key. Only hours depends on the whole key. Split:
employees (emp_id, emp_name, dept_id, dept_name)
assignments (emp_id, project_id, hours)
3NF: remove transitive dependencies. In employees, emp_id → dept_id and dept_id → dept_name, so dept_name depends on the key only throughdept_id. Split again:
employees (emp_id, emp_name, dept_id)
departments (dept_id, dept_name)
assignments (emp_id, project_id, hours)
Now each fact lives once: a name in employees, a department name in departments, hours in assignments.
BCNF: every determinant is a candidate key. 3NF still allows a dependency X → Y where Y is part of a key and X isn't a candidate key. Example: each topic in a training programme is taught by exactly one trainer, and each trainer teaches one topic; students attend topics. In (student, topic, trainer), the key is (student, topic), but trainer → topic and trainer isn't a candidate key. That breaks BCNF; splitting into (trainer, topic) and (student, trainer) fixes it.
Normal form
Rule, in one line
1NF
Atomic values; no repeating groups
2NF
1NF + no partial dependency on a composite key
3NF
2NF + no transitive dependency through a non-key column
BCNF
Every determinant is a candidate key
A memory aid often quoted for 3NF: every non-key column depends on "the key, the whole key, and nothing but the key".
Follow-ups
Can a table with a single-column key violate 2NF? No: partial dependency needs a composite key. It can still violate 3NF.
Do 4NF and 5NF matter? They deal with multi-valued and join dependencies, and rarely come up at this level.
09Transactions and concurrency
0/3 got it
Q33Not marked yet·Fresher·Asked very often·Concept
What is a transaction, and what does each ACID property guarantee?
Say this first~30 sec to say
A transaction is a group of statements that succeed or fail as one unit, like a bank transfer's debit and credit. ACID describes its guarantees. Atomicity: all of it happens or none of it does. Consistency: it takes the database from one valid state to another, respecting every constraint. Isolation: concurrent transactions don't see each other's half-finished work. Durability: once committed, it survives a crash. You control it with START TRANSACTION, COMMIT, ROLLBACK and SAVEPOINT.
If the server crashed between the two updates, or the second one failed, ROLLBACK (or crash recovery) undoes the first, so money is never debited without being credited.
SAVEPOINT: undo part of a transaction
SQL
STARTTRANSACTION;
UPDATE accounts SET balance = balance - 500WHERE account_id = 1;
SAVEPOINT after_fee;
UPDATE accounts SET balance = balance - 9999WHERE account_id = 1; -- fails the CHECK, or a mistakeROLLBACKTOSAVEPOINT after_fee; -- undo only the second updateCOMMIT; -- the 500 fee stays
Property
Guarantees
How databases provide it
Atomicity
All or nothing
Undo logs
Consistency
Constraints hold before and after
Constraints, triggers, and correct application logic
Isolation
Concurrent transactions don't interfere
Locks and MVCC (question 34)
Durability
Committed data survives crashes
Write-ahead (redo) logs flushed to disk
A MySQL-specific note: ACID applies to the InnoDB engine, MySQL 8's default. The older MyISAM engine has no transactions at all, which is the usual follow-up: "InnoDB vs MyISAM?" InnoDB adds transactions, row-level locking, foreign keys and crash recovery.
Follow-ups
What is autocommit? MySQL's default mode: each statement outside an explicit transaction is committed on its own.
Does every statement in MySQL obey transactions? No: DDL such as CREATE, ALTER, DROP and TRUNCATE commits implicitly (question 8).
What are dirty reads, non-repeatable reads and phantom reads, and which isolation level prevents each?
Say this first~30 sec to say
A dirty read sees another transaction's uncommitted changes, which may be rolled back. A non-repeatable read reads the same row twice and gets different values, because another transaction committed an update in between. A phantom read runs the same query twice and gets new or missing rows. The four standard isolation levels block progressively more: READ UNCOMMITTED allows all three, READ COMMITTED blocks dirty reads, REPEATABLE READ also blocks non-repeatable reads, and SERIALIZABLE blocks all three.
Could you say it?
Full answerHide full answer2 min read
Isolation level
Dirty read
Non-repeatable read
Phantom read
READ UNCOMMITTED
Possible
Possible
Possible
READ COMMITTED
Prevented
Possible
Possible
REPEATABLE READ
Prevented
Prevented
Possible in the standard; InnoDB prevents most cases
SERIALIZABLE
Prevented
Prevented
Prevented
Each problem as a timeline
Transaction A
Transaction B
Dirty read
UPDATE accounts SET balance = 0 WHERE account_id = 1; (not committed)
SELECT balance … ; reads 0
ROLLBACK;: the 0 never really existed
Non-repeatable read
SELECT salary FROM employees WHERE emp_id = 3; → 120000
UPDATE employees SET salary = 130000 WHERE emp_id = 3; COMMIT;
The same SELECT → 130000
Phantom read
SELECT COUNT(*) FROM orders WHERE status = 'placed'; → 1
Defaults differ: InnoDB defaults to REPEATABLE READ, using MVCC snapshots plus next-key locks for locking reads. PostgreSQL, SQL Server and Oracle default to READ COMMITTED.
The trade-off: stronger isolation means more locking or more aborted transactions, so less concurrency. Most applications run at the default and lock explicitly where it matters, for example with SELECT … FOR UPDATE when booking the last seat.
Follow-ups
What is MVCC? Multi-version concurrency control: readers see a consistent snapshot of committed versions, so reads don't block writes and writes don't block reads.
What is a lost update? Two transactions read a value and both write back changes based on it, so one overwrites the other. SELECT … FOR UPDATE, or an atomic UPDATE … SET stock = stock - 1, prevents it.
What is a deadlock between two transactions, and how does MySQL detect and resolve it?
Say this first~30 sec to say
A deadlock is two transactions each holding a lock the other needs, so both would wait forever: A locks row 1 and wants row 2, while B locks row 2 and wants row 1. InnoDB detects the cycle in its wait-for graph automatically, rolls back one transaction, usually the one that's cheaper to undo, and returns error 1213 to it; the other proceeds. The application should retry the failed transaction. Taking locks in a consistent order prevents most deadlocks.
Could you say it?
Full answerHide full answer2 min read
Step
Transaction A
Transaction B
1
UPDATE accounts … WHERE account_id = 1; locks row 1
2
UPDATE accounts … WHERE account_id = 2; locks row 2
3
UPDATE accounts … WHERE account_id = 2; waits for B
4
UPDATE accounts … WHERE account_id = 1; waits for A: a cycle
5
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
6
A's update goes through
B was rolled back; the app retries it
Shared vs exclusive locks, the usual lead-in: a shared (S) lock lets many transactions read a row at once; an exclusive (X) lock, taken for writes and SELECT … FOR UPDATE, blocks everyone else from locking that row.
Preventing deadlocks
Access rows and tables in the same order everywhere: for example, always lock the lower account ID first in a transfer.
Keep transactions short, and don't wait for user input inside one.
Use indexes on the columns in WHERE, so updates lock few rows instead of scanning and locking many.
Accept that some will still happen, and retry on error 1213.
Follow-ups
How do you investigate one?SHOW ENGINE INNODB STATUS shows the latest deadlock: both transactions, the locks held and the locks wanted.
Deadlock vs lock wait timeout? A deadlock is detected immediately; a plain wait gives up after innodb_lock_wait_timeout (50 seconds by default) with error 1205.
Do deadlocks happen in application code too? Yes, with threads and locks; the Java kit's question 38 covers them.
10Indexes
0/2 got it
Q36Not marked yet·Fresher·Asked very often·Concept
What is an index, and how do clustered and non-clustered indexes differ?
Say this first~25 sec to say
An index is a separate structure, usually a B+ tree, keeping column values sorted with pointers to their rows, so lookups and ranges skip the full scan. A clustered index stores the rows themselves in index order, so a table has only one. A non-clustered index stores the values plus a pointer back to the row. In InnoDB, the primary key is always the clustered index.
Could you say it?
Full answerHide full answer2 min read
Clustered
Non-clustered (secondary)
Leaf level holds
The full rows
The indexed columns + a row pointer
How many per table
One
Many
In InnoDB
Always the primary key
Every other index; the "pointer" is the primary key value
Lookup cost
Find the key: the row is right there
Find the key, then fetch the row (unless the index covers the query)
Good for
Range scans on the key, ordered reads
Lookups and filters on other columns
SQL
CREATEINDEX idx_employees_dept_salary ONemployees (dept_id, salary);
EXPLAINSELECT emp_name FROM employees WHERE dept_id = 2AND salary > 100000;
-- on a table of realistic size: type: range, key: idx_employees_dept_salary-- (on 10 rows the optimiser may simply scan the table: it's cheaper)
A covering index contains every column a query needs, so the database never visits the table rows: SELECT dept_id, salary FROM employees WHERE dept_id = 2 can be answered from idx_employees_dept_salary alone, shown as "Using index" in EXPLAIN.
How the two kinds fit together in InnoDB: a lookup by email searches the email index, finds the emp_id stored there, then searches the clustered primary-key index for the row. That's why a long primary key makes every secondary index bigger.
SQL Server lets you choose which index is clustered (it defaults to the primary key); InnoDB always clusters on the primary key, or a hidden row ID if a table has none. Why an index speeds up lookups at all, and what it costs on writes, is covered in the Backend kit's question 30.
Follow-ups
Why are B+ trees used rather than binary trees? Each node holds hundreds of keys, so the tree is only three or four levels deep even for millions of rows, which means few disk reads per lookup. Leaves are linked, which makes range scans fast.
What is a hash index? A structure for exact-match lookups only: no ranges, no sorting. InnoDB uses an adaptive hash index internally; MEMORY tables support them explicitly.
When can't a query use an index on a column, and why does column order matter in a composite index?
Say this first~30 sec to say
An index is sorted by the raw column values, so it can't help when the query hides the column: wrapping it in a function or calculation (YEAR(hire_date) = 2024, salary * 12 > …), a leading wildcard (LIKE '%@gmail.com'), or a type mismatch that forces conversion. In a composite index(dept_id, salary), rows are sorted by dept_id first, then salary, so it serves filters on dept_id, or on both, but not on salary alone: the leftmost-prefix rule.
Could you say it?
Full answerHide full answer2 min read
Query on employees
Uses an index on hire_date?
Rewrite
WHERE YEAR(hire_date) = 2024
No: the function hides the column
WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01'
WHERE salary * 12 > 1800000 (on a salary index)
No
WHERE salary > 150000
WHERE email LIKE '%@company.in' (on email)
No: leading wildcard
Store the domain in its own column, or use full-text search
WHERE email LIKE 'asha%'
Yes: a fixed prefix is a range
WHERE phone = 9876543210 on a VARCHAR column
Often not: the number forces conversion
WHERE phone = '9876543210'
The leftmost-prefix rule, with INDEX (dept_id, salary)
Filter
Index used?
WHERE dept_id = 2
Yes
WHERE dept_id = 2 AND salary > 100000
Yes: both columns
WHERE salary > 100000
No: salary is only sorted within each dept_id
WHERE dept_id = 2 ORDER BY salary
Yes, and the sort comes free
Think of a phone book sorted by surname, then first name: it finds "Sharma, Priya" instantly, and every Sharma, but it's no help for "everyone called Priya".
Choosing the column order: put columns tested with equality first and range columns last, and favour the column that appears in the most queries.
Checking with EXPLAIN
SQL
EXPLAINSELECT emp_name FROM employees WHEREYEAR(hire_date) = 2024;
-- type: ALL (a full scan), even with an index on hire_date
In EXPLAIN, type shows the access method: ALL is a full scan; range, ref and const use an index, from broad to precise. key names the index chosen, and rows estimates how many rows will be examined.
Follow-ups
Can MySQL index an expression? Yes, since 8.0.13 a functional index works: CREATE INDEX idx_hire_year ON employees ((YEAR(hire_date)));.
Does a low-selectivity column deserve an index? Rarely. On a column like status with three values, the optimiser often prefers a scan anyway.
What is a view, and can you insert or update data through one?
Say this first~30 sec to say
A view is a saved SELECT with a name, queried like a table; it stores no data of its own, so it always reflects the current rows. Views simplify complex joins, give a stable interface while tables change, and limit access, for example showing employees without their salaries. Simple single-table views are updatable: inserts and updates pass through to the base table. Views with GROUP BY, DISTINCT, aggregates, UNION or most joins are read-only.
Could you say it?
Full answerHide full answer2 min read
SQL
CREATEVIEW employee_directory ASSELECT e.emp_id, e.emp_name, e.email, d.dept_name
FROM employees e
LEFTJOIN departments d ON d.dept_id = e.dept_id; -- no salaries in hereSELECT * FROM employee_directory WHERE dept_name = 'Sales';
GRANTSELECTON company.employee_directory TO'intranet'@'%'; -- access to the view, not the table
Updatable or not
SQL
CREATEVIEW sales_team ASSELECT emp_id, emp_name, salary, dept_id FROM employees WHERE dept_id = 2WITHCHECK OPTION; -- rejects changes that would move a row out of the viewUPDATE sales_team SET salary = 165000WHERE emp_id = 7; -- allowed: updates employeesUPDATE sales_team SET dept_id = 3WHERE emp_id = 7; -- rejected by WITH CHECK OPTIONCREATEVIEW dept_payroll ASSELECT dept_id, SUM(salary) AS total FROM employees GROUPBY dept_id;
-- read-only: an UPDATE fails, because a total maps to no single row
View contains
Updatable?
One table, plain columns, optional WHERE
Yes
GROUP BY, aggregates, DISTINCT, HAVING
No
UNION
No
Joins
Sometimes, for columns of one base table, with restrictions
Materialized views store the query's result and refresh it on demand, which suits expensive reports. MySQL has no materialized views; you emulate them with a summary table refreshed by a scheduled event. PostgreSQL has CREATE MATERIALIZED VIEW, and SQL Server uses indexed views.
Follow-ups
Does a view make a query faster? Not by itself: it's expanded into the underlying query each time. A materialized view can.
What happens if the base table drops a column the view uses? The view becomes invalid and errors when queried until it's recreated.
Stored procedure vs function: how do they differ, and when would you keep logic in the database rather than in application code?
Say this first~30 sec to say
Both are named routines stored in the database. A stored procedure runs with CALL, takes IN, OUT and INOUT parameters, can return result sets and can manage transactions. A function returns one value and can be used inside a SELECT, but can't commit or roll back. Keep logic in the database when it's data-heavy or shared by several apps; keep business rules in application code, where they're easier to test and change.
Could you say it?
Full answerHide full answer3 min read
SQL
DELIMITER //
CREATEFUNCTIONannual_ctc(monthly DECIMAL(10,2))
RETURNSDECIMAL(12,2)
DETERMINISTICBEGINRETURN monthly * 12;
END //
CREATEPROCEDUREgive_department_raise(IN p_dept_id INT, IN p_percent DECIMAL(5,2), OUT p_updated INT)
BEGINSTARTTRANSACTION;
UPDATE employees
SET salary = ROUND(salary * (1 + p_percent / 100), 2)
WHERE dept_id = p_dept_id;
SET p_updated = ROW_COUNT();
COMMIT;
END //
DELIMITER ;
SELECT emp_name, annual_ctc(salary) AS yearly FROM employees WHERE dept_id = 3; -- a function, inside SELECTCALLgive_department_raise(3, 10, @updated); -- a procedure, with CALLSELECT @updated; -- 2 rows updated
DELIMITER // changes the statement terminator in the MySQL client, so the semicolons inside the body don't end the CREATE statement early.
Stored procedure
Function
Called with
CALL proc(...)
Inside a query: SELECT f(col)
Returns
Nothing, OUT parameters, or result sets
Exactly one value
Transactions
Can COMMIT and ROLLBACK
Can't
Can modify data
Yes
Avoid it; MySQL restricts it
Typical use
Batch jobs, multi-step operations
Reusable calculations
Database or application?
Keep it in the database when
Keep it in the application when
It processes lots of rows that shouldn't cross the network
It's business logic that changes often
Several applications must apply it identically
It needs unit tests and code review like other code
It enforces data integrity
It calls external services
You may change databases later
Follow-ups
Are stored procedures faster? Sometimes, by saving round trips for multi-step work, but most performance comes from good queries and indexes, wherever they're sent from.
Do procedures prevent SQL injection? Only if they use parameters internally; building dynamic SQL from strings inside a procedure is just as injectable.
What is a trigger, and when would you use one or avoid one?
Say this first~30 sec to say
A trigger is code the database runs automaticallyBEFORE or AFTER an INSERT, UPDATE or DELETE on a table, with access to the row's old and new values. Good uses: writing an audit trail, maintaining derived values, or enforcing a rule no constraint can express. Use them sparingly: they're hidden side effects that run on every matching change, making behaviour hard to trace, debug and test, and slowing bulk writes. In MySQL, triggers fire once per row.
OLD and NEW: in an UPDATE trigger, OLD holds the row before the change and NEW after it. INSERT triggers have only NEW; DELETE triggers only OLD. A BEFORE trigger can modify NEW to adjust the values being written.
Use a trigger for
Avoid a trigger for
Audit logs that must be written whatever app makes the change
Business workflows (sending emails, calling APIs)
Keeping a denormalized column in sync
Logic other developers need to see and test
Rules spanning tables that constraints can't express
Heavy work on high-volume tables
Database differences: MySQL triggers are row-level only (FOR EACH ROW). SQL Server triggers fire once per statement, with all affected rows in the inserted and deleted tables, so code written for one row breaks on multi-row updates. PostgreSQL supports both.
Follow-ups
Can a trigger stop the change? Yes: a BEFORE trigger can SIGNAL SQLSTATE '45000' with a message, which aborts the statement.
How many triggers can a table have? MySQL allows several per event and timing, ordered with FOLLOWS and PRECEDES.
Last step
Write these from memory
Before an SQL round, make sure you can write each of these on a blank page without help: