Contribute OA questions
OAHelper
CompaniesProblemsTopicsInterview Experiences
Explore
Week 02

Week 02

SQL Interview Theory: The Questions They Actually Ask

Day 122 hrs

Window Functions I: ROW_NUMBER, RANK & Top-N per GroupWindow Functions II: LAG/LEAD, Running Totals & GapsString & Date FunctionsHard Company Problems: Combining EverythingSQL Interview Theory: The Questions They Actually Ask

Explain the reason behind the query

Writing the right query is one part of an interview. You should also be able to explain why a key is needed, what an index helps with, and how concurrent updates can go wrong.

Start with a short answer and one example. For a composite key: "It uses more than one column to identify a row. In an enrolments table, student_id and course_code together identify one enrolment." Then discuss the trade-off or follow-up if asked. There is no need to recite every related definition at once.

There is no practice set today. Read this once, say the answers out loud, and open it again on the morning of your interview.


One schema to work through

This enrolment table repeats student, branch and course details. If a course fee changes, several rows need updating. If one is missed, the data disagrees with itself. We will use it to discuss keys, normalisation and query performance.

enrolments_raw, the college keeping its whole record in a single sheet:

student_idnamebranchhodcourse_codecourse_namefeemarks
1AshaCSEProf. RaoCS301DBMS500078
1AshaCSEProf. RaoCS302OS400081
2RaviITProf. IyerCS301DBMS500065
3NehaCSEProf. RaoCS302OS400074
4KarthikITProf. IyerCS301DBMS500088
5MeeraECEProf. NairCS302OS4000NULL

Six rows, one per student and course pair, and Asha owns two because she takes two subjects. four different things are wrong here.

student_id alone cannot identify a row, because Asha appears twice. name and branch are copied on every row of the same student. fee has nothing to do with the student, it belongs to the course. hod belongs to the branch, not to the student.

Meera's NULL marks mean no mark is recorded. Keep that separate from zero; it will matter when we calculate averages and check conditions.


The Pattern

Six layers, from the table definition upward. When the interviewer jumps around, you are only moving up and down this stack.

  1. 1Keys
  2. 2Normalization
  3. 3Indexes
  4. 4Transactions
  5. 5Isolation
  6. 6Execution order

1. Keys, how a row is identified

A key is a column, or set of columns, whose value tells you exactly which row you mean. Without one, "delete Asha's DBMS entry" is not a well defined instruction.

A super key is any column set that identifies a row, minimal or not. A candidate key is a super key with nothing spare in it. In enrolments_raw the only candidate key is (student_id, course_code).

You pick one candidate key and call it the primary key: unique, never NULL, exactly one per table. The ones you did not pick are alternate keys, enforced with UNIQUE, which still allows NULL. A key built from two or more columns is a composite key.

A foreign key requires a non-NULL reference to match a key in the referenced table. It can reference a primary or suitable unique key, including one in the same table. The chosen delete action decides whether deleting a parent is blocked, cascades to children, or changes the reference.

Tip

Tip: A surrogate key is an id with no business meaning. A natural key comes from the data itself. A surrogate id is useful when business identifiers may change, but you may still need a unique constraint on the natural identifier.

2. Normalization, store every fact exactly once

Before the sheet above, the clerk kept one row per student with course_code holding "CS301, CS302" and marks holding "78, 81". That is unnormalised. Split those cells and you reach 1NF: atomic values, no repeating groups, one row per student and course. The table above is that 1NF table.

2NF removes partial dependencies, meaning a non-key column depending on only part of the composite key. Here name, branch and hod depend on student_id alone, course_name and fee on course_code alone. Pull both groups out.

Students

student_idnamebranchhod
1AshaCSEProf. Rao
2RaviITProf. Iyer
3NehaCSEProf. Rao
4KarthikITProf. Iyer
5MeeraECEProf. Nair

Courses

course_codecourse_namefee
CS301DBMS5000
CS302OS4000

Enrolments

student_idcourse_codemarks
1CS30178
1CS30281
2CS30165
3CS30274
4CS30188
5CS302NULL

The fee now exists in exactly one place, and Enrolments keeps only what is truly about the pair, the marks.

3NF removes transitive dependencies, a non-key column depending on another non-key column. Inside Students, hod depends on branch, and branch is not a key. One more split.

Students

student_idnamebranch
1AshaCSE
2RaviIT
3NehaCSE
4KarthikIT
5MeeraECE

Branches

branchhod
CSEProf. Rao
ITProf. Iyer
ECEProf. Nair

Three student rows in CSE and IT now point at one HOD row each, instead of carrying a copy of the name.

Now nothing is stored twice and the three anomalies interviewers name are gone. Change the DBMS fee in one place, no row left behind, that is the update anomaly. Add a course before anybody enrols, the insert anomaly. Remove the last ECE student without losing Prof. Nair, the delete anomaly.

BCNF strengthens 3NF: for every non-trivial functional dependency, the determining columns must form a superkey. In other words, a set of columns should not determine other facts unless it can identify the row.

3. Indexes, how rows are found fast

An index provides another way to find rows without scanning the whole table. A B-tree lookup takes roughly logarithmic work to find a starting point, then additional work to read the matching entries and rows.

A common index structure is a B-tree. Its ordered entries support equality checks, ranges and some ordering requirements. Hash indexes mainly support equality lookups. The useful choice depends on the query and database.

An index may helpCheck before assuming it helps
WHERE or JOIN on that columnthe query returns most of the table
ORDER BY or GROUP BY in that column orderthe column is wrapped in a function
MIN and MAXa leading wildcard, like LIKE '%kar'
covering reads, when every column needed is in the indexthe column has few distinct values

Two useful points to explain:

Leading columns. An index on (course_code, marks) is ordered by course first and marks within that course. It is a natural fit for a course filter, possibly followed by a marks range. A marks-only filter is less direct; the planner may scan the index or use another optimisation. Check the plan rather than treating this as an absolute rule.

Cost. Every insert, update and delete maintains every index on the table, so an index is never free.

A clustered index stores the table data in its index structure in engines that support it. Secondary indexes provide another route to those rows. The details differ: MySQL InnoDB organises data around the primary key, while PostgreSQL does not maintain that same clustered layout automatically.

4. Transactions and ACID

A transaction is a group of statements that must happen as one unit: BEGIN, do the work, then COMMIT to keep it or ROLLBACK to undo all of it. SAVEPOINT marks a spot inside it that you can roll back to partially.

LetterPropertyOne line
AAtomicityall statements or none, so a debit can never happen without its credit
CConsistencyvalid state to valid state, every constraint and key still holds at the end
IIsolationconcurrent transactions do not see each other's half done work
DDurabilityonce committed, a power cut cannot undo it

The four letters are one bank transfer told from four angles, which is how you should say it out loud.

5. Isolation levels

Stronger isolation restricts which concurrent outcomes are allowed. It can add waiting or retries. This table shows the anomalies permitted by the SQL standard; actual database behaviour can be stronger.

LevelDirty readNon-repeatable readPhantom read
READ UNCOMMITTEDpossiblepossiblepossible
READ COMMITTEDnopossiblepossible
REPEATABLE READnonopossible
SERIALIZABLEnonono

A dirty read sees another transaction's uncommitted change. A non-repeatable read is the same SELECT twice giving a different value. A phantom read is the same SELECT twice giving a different row count, because somebody inserted a row that matches your condition.

PostgreSQL defaults to READ COMMITTED and MySQL InnoDB to REPEATABLE READ. PostgreSQL's REPEATABLE READ also prevents phantom reads, although it still allows serialization anomalies. SQLite normally serialises writes and provides snapshot isolation in WAL mode. These details matter when discussing a real application. The PostgreSQL isolation documentation explains its guarantees.

6. Execution order, the fence

The clause you write first is not the clause that runs first. Build the query up in three steps and you can see it happen.

  1. 1FROM + JOIN
  2. 2WHERE
  3. 3GROUP BY
  4. 4HAVING
  5. 5SELECT
  6. 6ORDER BY
  7. 7LIMIT

Step 1, just read the column. SELECT branch FROM Students; gives five rows, one per student: CSE, IT, CSE, IT, ECE. Nothing is folded yet.

Step 2, group them. GROUP BY takes rows sharing a value and folds them into one row per value, so you can count each pile.

SQL
SELECT branch, COUNT(*) AS students
FROM Students
GROUP BY branch;
branchstudents
CSE2
ECE1
IT2

Five rows have become three, and COUNT(*) is how many original rows went into each pile.

Step 3, filter the piles and sort them. HAVING filters groups, the way WHERE filters rows.

SQL
SELECT branch, COUNT(*) AS students
FROM Students
WHERE branch IS NOT NULL
GROUP BY branch
HAVING COUNT(*) >= 2
ORDER BY students DESC, branch ASC;
branchstudents
CSE2
IT2

Read the query line by line:

  1. FROM Students reads the five student rows.
  2. WHERE branch IS NOT NULL drops rows with no branch, one row at a time. Here nothing is dropped.
  3. GROUP BY branch folds those rows into one pile per branch, so three piles.
  4. HAVING COUNT(*) >= 2 drops whole piles, so ECE with its single student is gone.
  5. SELECT branch, COUNT(*) AS students builds the output columns, and the name students is born right here.
  6. ORDER BY students DESC, branch ASC sorts what survived, and the alias is finally visible.

Dry Run, the same query counted step by step:

StepWhat happensRows alive
FROM Studentsread the table5
WHERE branch IS NOT NULLper row filter5
GROUP BY branchfold into groups3, CSE and IT and ECE
HAVING COUNT(*) >= 2per group filter2, ECE is dropped here
SELECT branch, COUNT(*) AS studentsbuild the output columns2
ORDER BY students DESC, branch ASCsort the result2

CSE and IT tie on 2, so the second sort key branch ASC is what fixes the order between them. Drop it and the rows can come out either way on a rerun, which is how a query passes on your laptop and fails on the judge.

What an OA judge sees. Judges never read your query. They capture the printed rows and compare them as text against a saved expected output:

Text
CSE|2
IT|2

Pipe separated, no header, no padding, in exactly the order your ORDER BY produced. The practice judge here works the same way, on SQLite. Get the order or the decimals wrong and the case fails, even with correct logic.

Three consequences of the fence, asked constantly. WHERE runs before GROUP BY, so it can never hold an aggregate. A SELECT alias is invisible to WHERE, GROUP BY and HAVING, but visible to ORDER BY. LIMIT is last, so it cuts the sorted result, not the scan.

Check what you learned

In the query above, you replace HAVING COUNT(*) >= 2 with WHERE students >= 2. What happens?


Query Templates

SQL

SQL
-- The 3NF schema in one go: primary key, composite key, foreign keys, unique, index.
CREATE TABLE Branches (
  branch TEXT PRIMARY KEY,
  hod    TEXT NOT NULL
);
CREATE TABLE Students (
  student_id INTEGER PRIMARY KEY,             -- surrogate primary key
  name       TEXT NOT NULL,
  email      TEXT UNIQUE,                     -- alternate key: unique, but NULL allowed
  branch     TEXT REFERENCES Branches(branch) -- foreign key
);
CREATE TABLE Courses (course_code TEXT PRIMARY KEY, course_name TEXT NOT NULL, fee INTEGER NOT NULL);
CREATE TABLE Enrolments (
  student_id  INTEGER NOT NULL REFERENCES Students(student_id) ON DELETE CASCADE,
  course_code TEXT    NOT NULL REFERENCES Courses(course_code),
  marks       INTEGER,                        -- NULL means the student was absent
  PRIMARY KEY (student_id, course_code)       -- composite key
);
CREATE INDEX idx_enrol_course ON Enrolments(course_code, marks);

-- A transaction: all of it, or none of it.
BEGIN TRANSACTION;
  UPDATE Accounts SET balance = balance - 500 WHERE id = 1;
  UPDATE Accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;                                       -- ROLLBACK; undoes both

-- A view is a saved query, not stored data.
CREATE VIEW v_branch_strength AS
  SELECT b.branch, b.hod, COUNT(s.student_id) AS students
  FROM Branches b LEFT JOIN Students s ON s.branch = b.branch
  GROUP BY b.branch, b.hod;

That index is not decoration, and you can prove it in ten seconds. EXPLAIN QUERY PLAN prints the plan the engine chose instead of running the query.

SQL
EXPLAIN QUERY PLAN
SELECT course_code, marks FROM Enrolments WHERE course_code = 'CS301';
Text
SEARCH Enrolments USING COVERING INDEX idx_enrol_course (course_code=?)

SEARCH means it jumped straight to the matching entries. COVERING means both columns were read out of the index itself and the table was never touched. Drop the index and the same line becomes SCAN Enrolments, which is the engine reading every row.

With WHERE marks = 78, this example can use SCAN Enrolments USING COVERING INDEX idx_enrol_course. It reads the index rather than searching a specific course range. Plans depend on data and statistics; SQLite also has optimisations such as skip-scan for some non-leading-column filters. See the SQLite optimiser notes.


Variations

Variation 1 - DELETE vs TRUNCATE vs DROP

Start with what each command removes, then mention the database when discussing rollback and triggers.

DELETETRUNCATEDROP
TypeDMLDDLDDL
Removesselected rowsall rowsrows plus the table itself
WHEREyesnono
Work involveddeletes matching rows; optimisations varygenerally avoids individual row deletionremoves the table and its data
Rollbackdepends on transaction supportengine-specificengine-specific
Triggersrow delete triggers can runno row delete triggers; dedicated triggers varyengine-specific DDL behaviour
Identity countergenerally retainedengine and options determine reset behaviourremoved with the table

SQLite has no TRUNCATE command. It can optimise a DELETE without WHERE when the required conditions hold. In other engines, do not infer rollback or trigger behaviour from the DDL/DML label alone.

Variation 2 - UNION vs UNION ALL

UNION stacks the output of two queries into one list. Toppers and backlog cases from Enrolments, together:

SQL
SELECT course_code FROM Enrolments WHERE marks >= 80
UNION ALL
SELECT course_code FROM Enrolments WHERE marks < 70;
course_code
CS301
CS302
CS301

Karthik's 88 in CS301 and Asha's 81 in CS302 from the top half, Ravi's 65 in CS301 from the bottom half. Meera's NULL fails both >= 80 and < 70, so she appears in neither. That is three valued logic doing its job, not a bug.

Swap UNION ALL for UNION and you get two distinct values, CS301 and CS302. That does not guarantee their output order; add ORDER BY when order matters. Use UNION ALL when duplicates should remain or are already ruled out.

Both forms need the same column count and compatible types, names come from the first SELECT, and one ORDER BY at the very end applies to the combined result.

For JOIN vs subquery, first check whether both forms return the same rows. The optimiser may transform either form, so compare plans before claiming one is faster. For EXISTS vs IN, be especially careful with negative checks: a NULL returned by a NOT IN subquery can change the result, while NOT EXISTS tests whether a matching row is present.

Variation 3 - NULL, and the rest of the syllabus

NULL is not a value, it is "unknown", so SQL is three valued: TRUE, FALSE, UNKNOWN. NULL = NULL is UNKNOWN, which is why you write IS NULL. WHERE keeps only TRUE rows, so UNKNOWN rows vanish silently with no error.

SQL
SELECT COUNT(*) AS rows_seen, COUNT(marks) AS graded, AVG(marks) AS avg_marks
FROM Enrolments;
rows_seengradedavg_marks
6577.2

COUNT(*) counts rows, COUNT(marks) counts non NULL values, and AVG follows the second one, dividing by 5. With these integer marks, SUM(marks) / COUNT(*) divides by 6 and truncates to 64 in SQLite. Even with decimal division, the denominator would still be wrong. The judge prints 77.2, not 77.20, so use ROUND(...) only when the statement asks for it.

GROUP BY and DISTINCT are the two places where NULLs are treated as equal, and all NULLs fall into one group.

CHAR vs VARCHAR. CHAR(10) is fixed width and space padded, so 'IT' is compared as 'IT ', while VARCHAR(10) stores only what you gave it. SQLite ignores the length and treats every text column as TEXT.

Views vs materialized views. A view is a stored query with no data of its own, so it re-runs every time and is always current. A materialized view stores the result on disk, so a dashboard may read it more cheaply, but it is only as fresh as the last refresh. Postgres and Oracle have them, MySQL and SQLite do not.

Stored procedures and triggers. A procedure is a named block of SQL living inside the database, called with CALL; a function is the same idea but must return a value. A trigger is a block the database fires by itself BEFORE or AFTER an INSERT, UPDATE or DELETE, used for audit logs and rules a constraint cannot express. Triggers are invisible, so a bad one makes a plain insert mysteriously slow or fail.

Relational vs NoSQL databases. Relational databases organise data in tables with keys, constraints and joins. NoSQL covers several models, including documents, key-value pairs and graphs. Transactions, consistency guarantees and scaling options vary by product. Choose based on the data relationships, access patterns and guarantees the application needs.

Check what you learned

Nobody scored 90 in either course. What does SELECT course_name FROM Courses c WHERE 90 NOT IN (SELECT marks FROM Enrolments e WHERE e.course_code = c.course_code); return?


Common Mistakes

  • Giving only a definition. Add a small example: a composite key on (student_id, course_code) is easier to discuss than a memorised sentence alone.
  • Saying an index always helps. Explain the access pattern, write cost and storage cost. Check the plan before claiming an improvement.
  • Ignoring index column order. An index on (course_code, marks) is a natural fit for a course filter. A marks-only filter may need another index or a different plan.
  • Using HAVING for a simple row filter. Put ordinary row conditions in WHERE. Use HAVING for conditions on grouped results.
  • Overlooking NULL in NOT IN. A NULL in the subquery can make the condition UNKNOWN. NOT EXISTS is often clearer when checking for missing matches.
  • Giving one rollback rule for every database. Name the engine. For example, MySQL TRUNCATE commits implicitly; PostgreSQL can roll it back in a transaction.
  • Assuming the result is already sorted. Only a final ORDER BY guarantees output order.
  • Quoting an isolation table as universal behaviour. The SQL standard describes permitted anomalies. A database may provide stronger guarantees at a named level.

Interview Notes

Primary key vs unique constraint? A table has at most one primary key, which identifies its rows. It can have several unique constraints. For a nullable unique column, check the engine's NULL rules rather than assuming they are identical everywhere. In SQLite, declare NOT NULL explicitly on non-integer primary-key columns in ordinary tables because of its legacy NULL behaviour.

Can a foreign key be NULL? Yes, if the column allows NULL. A non-NULL reference must match the referenced key. Deleting the parent follows the configured action, such as blocking the delete, cascading it or setting the reference to NULL.

Why normalise, and when would you stop? Normalisation reduces redundant facts and the update anomalies they cause. Start with the dependencies in the data. If a reporting workload needs a summary table or deliberate duplication, explain how it will stay in sync.

Why not index everything? Indexes need storage and maintenance when data changes. Choose them for actual filters, joins and ordering requirements, then inspect the plan and measure the workload.

Explain ACID with an example. For a transfer of 500 rupees, atomicity keeps the debit and credit together. Consistency means the transaction preserves the required constraints and business rules. Isolation controls concurrent interactions. Durability preserves a committed result under the database's configured guarantees.

Which isolation level would you choose? First identify what concurrent operations must not do: overspend a balance, reserve the last seat twice, or read an inconsistent report. Then choose constraints, locking and an isolation level for that requirement. Stronger isolation may require retries, so discuss those too.

What would you check in a slow query? Start with the plan: row estimates, scans, join sizes, sorts and useful indexes. Check whether a join multiplies more rows than expected. Compare alternative queries only after confirming they mean the same thing. EXPLAIN ANALYZE, where supported, executes the query and reports actual work.

40 rapid-fire questions with one-line answers

  1. DBMS vs RDBMS? An RDBMS keeps data in related tables with keys and constraints.
  2. DDL, DML, DCL, TCL? CREATE and DROP; SELECT and UPDATE; GRANT and REVOKE; COMMIT and ROLLBACK.
  3. Candidate key? The minimal column set that identifies a row uniquely.
  4. Super key? Any column set that identifies a row, minimal or not.
  5. Composite key? A key of two or more columns, like (student_id, course_code).
  6. Alternate key? A candidate key you did not make the primary key.
  7. Surrogate key? A generated id with no business meaning, preferred because business data changes.
  8. Referential integrity? A foreign key value must exist in the parent, or be NULL.
  9. ON DELETE CASCADE? Delete the parent and its child rows go too.
  10. 1NF? Atomic cells, no repeating groups.
  11. 2NF? 1NF plus nothing depending on part of a composite key.
  12. 3NF? 2NF plus no non-key column depending on another non-key column.
  13. BCNF? For each non-trivial dependency, the determinant must be a superkey.
  14. De-normalization? Duplicating data on purpose so reads skip the joins.
  15. Update anomaly? One fact in many rows, so a missed row breaks consistency.
  16. Index? An additional access structure that can make lookups cheaper.
  17. Clustered vs non-clustered? Clustered stores table data in the index structure; secondary indexes provide other access paths. Details depend on the engine.
  18. B-tree vs hash? B-tree does ranges and sorting, hash does equality only.
  19. When does an index hurt? Write heavy tables, low cardinality columns, wide result sets.
  20. Leftmost prefix? Leading columns determine the most direct lookup paths; check the plan for other filters.
  21. ACID? Atomicity, Consistency, Isolation, Durability.
  22. Transaction? Statements that commit or roll back together.
  23. COMMIT, ROLLBACK, SAVEPOINT? Keep it, undo it, mark a point to undo back to.
  24. Dirty read? Reading an uncommitted change.
  25. Non-repeatable read? Same row twice, different value.
  26. Phantom read? Same query twice, different row count.
  27. Which level stops all three? SERIALIZABLE.
  28. Default isolation level? READ COMMITTED in Postgres, REPEATABLE READ in MySQL InnoDB.
  29. DELETE vs TRUNCATE? DELETE can select rows; TRUNCATE empties the table. Rollback, triggers and identity handling depend on the engine.
  30. TRUNCATE vs DROP? TRUNCATE empties the table, DROP removes it.
  31. WHERE vs HAVING? Rows before grouping, groups after.
  32. Execution order? FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT.
  33. UNION vs UNION ALL? UNION removes duplicate result rows; UNION ALL keeps them. Neither guarantees output order.
  34. INNER vs LEFT JOIN? Matches only, versus every left row padded with NULL.
  35. CROSS JOIN? Every row with every row, the Cartesian product.
  36. Self join? A table joined to itself with two aliases, like employee to manager.
  37. CHAR vs VARCHAR? Fixed width and space padded, versus only what you gave it.
  38. COUNT(*) vs COUNT(col)? Rows, versus non-NULL values.
  39. View vs materialized view? A view reads through a saved query; a materialized view stores a result that needs refreshing.
  40. SQL vs NoSQL? Relational tables versus models such as documents, key-value pairs and graphs; compare the actual guarantees and workload.
Tip

Tip: Use these as revision prompts. Give a short answer, then explain one example or limitation. If you are unsure, say which part needs checking.


Quick Test

Check what you learned

In enrolments_raw, which column set is the only candidate key?

1/8

Day 12

Finished this topic?

Mark it done and your roadmap moves to the next day.

Up nextWeek 2 problem set
PreviousHard Company Problems: Combining Everything

On this page

Summarise with AI

  • ChatGPT
  • Claude
  • Perplexity
  • Grok
  • Google AI Mode
Your next OA is closer than you think.Real questions from this season's OAs
ProductCompany OAsAll ProblemsOA CalendarMock OAsDSA RoadmapSQL RoadmapAgent Engineering
ResourcesInterview experiencesTopicsOA GroupsContributeCompany InsightsOA StorePremiumFor Colleges
OA Helper, powered by NxtWave
TermsPrivacyRefundsTrust & SafetyContact© 2026 OA Helper

Disclaimer: OAHelper is an independent educational platform. We (oahelper.in) do not own the images or questions shown. Content is uploaded by users.