40 questions with answers for candidates with 2 years of experience — 5 easy, 34 medium, 1 hard.
What interviewers expect: 2 years experience
At two years, interviews go deeper into window frames, recursive CTEs, query performance, execution plans and transactions. You will face scenario questions such as wrong totals after a join or a slow report. Interviewers expect you to reason about grain, data quality and trade-offs, not only syntax.
This set is weighted towards Indexes and Performance (8), Joins (7), Aggregation and Grouping (6), Transactions and Advanced SQL (5).
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
A LEFT JOIN is returning fewer rows than the left table. Why could that happen?
A LEFT JOIN never removes left rows by itself, so a WHERE condition on the right table is likely filtering out rows where it is NULL. For example WHERE o.status = 'Paid' drops customers without orders. Move the condition into the ON clause or add OR o.status IS NULL. Another cause is a later INNER JOIN in the chain that removes rows.
In standard SQL, GROUP BY and HAVING are evaluated before SELECT, so aliases are not available there. MySQL and PostgreSQL allow aliases in GROUP BY, and MySQL allows them in HAVING, but SQL Server does not. For portable code, repeat the expression or wrap the query in a subquery or CTE and filter on the alias in the outer query.
03
How do you get the top 3 rows in different databases?
MySQL and PostgreSQL:
SELECT * FROM products ORDER BY price DESC LIMIT 3;
SQL Server:
SELECT TOP 3 * FROM products ORDER BY price DESC;
Standard SQL:
SELECT * FROM products ORDER BY price DESC
FETCH FIRST 3 ROWS ONLY;
Always use ORDER BY, otherwise the top rows are not guaranteed.
A materialised view stores the result of a query physically, unlike a normal view. Reads are fast because data is precomputed, which suits heavy aggregations for dashboards. The trade-off is staleness, so it must be refreshed on schedule, on commit or incrementally depending on the database. PostgreSQL uses REFRESH MATERIALIZED VIEW, and SQL Server offers indexed views with similar benefits.
05
What is the difference between clustered and non-clustered indexes?
A clustered index defines the physical order of rows in the table, so there can be only one, and in SQL Server the primary key creates it by default. A non-clustered index is a separate structure with key values and pointers to rows, and a table can have many. Range queries on the clustered key are very fast, while non-clustered indexes suit selective lookups.
06
Find departments where every employee earns above 40000.
SELECT d.department_id, d.name
FROM departments d
WHERE NOT EXISTS (
SELECT 1 FROM employees e
WHERE e.department_id = d.department_id
AND e.salary <= 40000
)
AND EXISTS (
SELECT 1 FROM employees e WHERE e.department_id = d.department_id
);
Double negation expresses for all; the EXISTS excludes empty departments.
07
Calculate a 7-day moving average of daily active users.
SELECT activity_date, dau,
ROUND(AVG(dau) OVER (
ORDER BY activity_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
), 1) AS dau_7d_avg
FROM daily_users
ORDER BY activity_date;
This assumes one row per day; fill missing dates with a calendar table first, or ROWS will span more than seven days.
08
How would you design a database for an online food delivery app at a high level?
Core tables are users, addresses, restaurants, menu_items, orders, order_items, payments, delivery_partners and deliveries. Orders reference users, restaurants and addresses, order_items reference orders and menu_items with price at order time, and payments and deliveries reference orders. Add status history tables for tracking and indexes on user_id, restaurant_id and created_at for common queries.
09
What is the difference between a CTE, a subquery, a temp table and a view?
A subquery and a CTE exist only for one statement; a CTE is more readable and can be recursive. A temporary table stores data physically for the session, can be indexed and reused across statements, useful for large intermediate results. A view is a saved query stored in the database that anyone with access can reuse. Choose by reuse, size and performance needs.
10
Create a simple stored procedure that returns orders for a customer.
MySQL:
DELIMITER //
CREATE PROCEDURE get_orders(IN p_customer INT)
BEGIN
SELECT order_id, order_date, amount
FROM orders
WHERE customer_id = p_customer
ORDER BY order_date DESC;
END //
DELIMITER ;
CALL get_orders(101);
Parameters prevent SQL injection compared with building query strings in code.
11
What is the difference between OLTP and OLAP databases?
OLTP systems handle many short transactions such as orders and payments, with normalised schemas, row-based storage and focus on fast inserts and updates. OLAP systems support analysis over large history, using denormalised star or snowflake schemas, columnar storage and heavy aggregations. A bank's core system is OLTP, while its data warehouse used for monthly reports is OLAP.
WITH m AS (
SELECT DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS revenue
FROM orders GROUP BY 1
)
SELECT month, revenue,
ROUND(100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))
/ NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 2) AS mom_pct
FROM m ORDER BY month;
NULLIF avoids division by zero.
13
How do you read an EXPLAIN ANALYZE output in PostgreSQL?
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 101;
Read the tree from the innermost node outward. Check node types such as Seq Scan, Index Scan or Hash Join, compare estimated rows with actual rows, and look at actual time per node. Large estimate errors suggest stale statistics, and Seq Scan on a selective filter suggests a missing index.
A SARGable (Search ARGument able) predicate lets the optimiser use an index seek. Wrapping an indexed column in a function, such as WHERE YEAR(order_date) = 2025 or WHERE LOWER(email) = 'x', usually prevents index use. Rewrite as WHERE order_date >= '2025-01-01' AND order_date < '2026-01-01'. Leading wildcards and implicit type conversions also make predicates non-SARGable.
15
What is the difference between FULL OUTER JOIN and UNION of LEFT and RIGHT joins?
A FULL OUTER JOIN returns matched rows plus unmatched rows from both sides in one operation. MySQL does not support it, so it is emulated with a LEFT JOIN UNION a RIGHT JOIN, or LEFT JOIN UNION ALL a RIGHT JOIN filtered to unmatched rows. Using plain UNION removes duplicates but can also remove genuinely repeated rows, so the UNION ALL pattern is safer.
16
A COUNT(DISTINCT) query on a huge events table is very slow. What options do you have?
Exact distinct counts need sorting or hashing all values, which is expensive. Options are filtering the date range tightly, pre-aggregating daily distinct users into a summary table, indexing the columns involved, or using approximate functions such as APPROX_COUNT_DISTINCT or HyperLogLog where small error is acceptable. Explain the accuracy trade-off to stakeholders before switching to approximations.
17
Tell me about a time you significantly improved query performance.
Situation: a daily settlement report took 45 minutes and often missed its deadline. Task: bring it under ten minutes. Action: I studied the execution plan, replaced a non-SARGable date filter, added a composite covering index, pre-aggregated a large join in a temp table and updated statistics. Result: runtime dropped to six minutes, and the finance team received reports on time every day.
AVG ignores NULLs, dividing the sum of non-null values by the count of non-null values. If three employees have bonuses 1000, 2000 and NULL, AVG(bonus) is 1500, not 1000. If NULL should mean zero, use AVG(COALESCE(bonus, 0)). Interviewers ask this to check whether you understand how missing data changes business metrics.
19
What is the difference between putting a condition in ON versus WHERE in a LEFT JOIN?
In a LEFT JOIN, a condition in ON filters the right table before matching, so all left rows are still kept. A condition on the right table in WHERE runs after the join and removes rows where the right side is NULL, effectively turning it into an inner join. To get all customers with only their 2025 orders, put the date condition in ON.
20
A business user needs a dashboard on a complex 6-table join that runs slowly. Would you use a view or a materialised view?
A normal view only simplifies the query; it still runs the full join every time, so performance does not improve. If the data can be a few minutes or hours old, a materialised view or summary table refreshed on schedule gives fast reads. If real-time data is required, tune the underlying query with indexes or build an incrementally maintained table.
21
Management wants the percentage of each category in total sales. How do you write it?
Use a window aggregate over the grouped result: SELECT category, SUM(sales) AS sales, ROUND(100.0 * SUM(sales) / SUM(SUM(sales)) OVER (), 2) AS pct FROM sales GROUP BY category. The inner SUM is per category and the outer SUM over an empty window gives the grand total. Multiplying by 100.0 avoids integer division in some databases.
A covering index contains all columns a query needs, either as key columns or included columns, so the database answers the query from the index alone without visiting the table. For SELECT order_date, amount FROM orders WHERE customer_id = 5, an index on (customer_id) INCLUDE (order_date, amount) avoids lookups. It speeds reads but increases index size and write cost.
23
What does DISTINCT do and how is it different from GROUP BY?
DISTINCT removes duplicate rows from the result based on all selected columns. GROUP BY groups rows so you can apply aggregates per group. SELECT DISTINCT city FROM customers and SELECT city FROM customers GROUP BY city give the same output, but GROUP BY is needed when you also want counts or sums. Overusing DISTINCT to hide duplicate join rows is a common mistake.
Denormalisation intentionally adds redundancy, such as storing customer name in an orders reporting table, to reduce joins and speed up reads. It is common in data warehouses, reporting tables and high-read applications. The cost is extra storage and the need to keep copies consistent through ETL, triggers or application logic. Use it when read performance matters more than write simplicity.
25
Match each employee's salary to a grade using a range table.
SELECT e.name, e.salary, g.grade
FROM employees e
JOIN salary_grades g
ON e.salary BETWEEN g.min_salary AND g.max_salary;
This is a non-equi join. Ensure grade ranges do not overlap, otherwise an employee matches more than one grade and appears twice.
26
You need customers who bought both product A and product B. How would you write it?
Group by customer and count distinct matching products: SELECT customer_id FROM order_items WHERE product_id IN ('A', 'B') GROUP BY customer_id HAVING COUNT(DISTINCT product_id) = 2. Alternatively use INTERSECT of two queries, or two EXISTS conditions. The HAVING approach is concise, scans the table once and is easy to extend to three or more products.
A join combines rows from two or more tables based on a related column. INNER JOIN returns matching rows only. LEFT JOIN returns all rows from the left table with matches or NULLs. RIGHT JOIN does the opposite. FULL OUTER JOIN returns all rows from both. CROSS JOIN returns every combination. A SELF JOIN joins a table to itself, such as employees and managers.
Use string aggregation: STRING_AGG(name, ', ') in PostgreSQL and SQL Server, GROUP_CONCAT(name SEPARATOR ', ') in MySQL, and LISTAGG in some other databases. Most support ordering inside, for example STRING_AGG(name, ', ' ORDER BY name). It is used to list products in an order or skills of an employee in one row, but watch length limits.
29
What are database statistics and why do they matter?
Statistics describe data distribution in columns, such as row counts, distinct values and histograms. The optimiser uses them to estimate how many rows each step returns and pick join types and indexes. Stale statistics after large data loads lead to bad plans, such as nested loops over millions of rows. Update them with ANALYZE in PostgreSQL or UPDATE STATISTICS in SQL Server.
30
What are equi joins, non-equi joins and natural joins?
An equi join uses equality in the condition, such as a.id = b.id, the most common case. A non-equi join uses other operators like BETWEEN or greater than, for example matching salary to a grade range table. A natural join automatically joins on columns with the same names, which is risky because adding a column can silently change results, so explicit ON is preferred.
31
Tell me about a production incident involving a database and how you handled it.
Situation: during a sale, order placement started failing with lock timeouts. Task: restore service quickly. Action: I identified a long-running reporting query holding locks on the orders table, coordinated with the team to kill it, and moved that report to the read replica. Result: orders recovered within ten minutes, and we added query timeouts and a rule that reports never run on the primary.
Isolation levels balance consistency and concurrency. Read Uncommitted allows dirty reads. Read Committed, the default in PostgreSQL and SQL Server, prevents dirty reads but allows non-repeatable reads. Repeatable Read, the default in MySQL InnoDB, prevents non-repeatable reads, and InnoDB also prevents most phantoms with gap locks. Serializable prevents all anomalies but reduces concurrency. Choose based on business correctness needs.
33
A reporting team wants to query the production OLTP database directly. What are the risks and alternatives?
Heavy analytical queries can lock rows, use CPU and slow customer transactions, and normalised schemas make reporting complex. Alternatives are a read replica for reporting, a data warehouse loaded by ETL or CDC with a star schema, or materialised summary tables. Grant read-only access with limits if direct access is unavoidable, and schedule heavy jobs off-peak.
34
Create an index to speed up searching orders by customer and date.
CREATE INDEX idx_orders_cust_date
ON orders (customer_id, order_date);
This helps queries like:
SELECT * FROM orders
WHERE customer_id = 101
AND order_date >= '2025-01-01';
Check with EXPLAIN that the index is used, and avoid creating duplicate indexes on the same columns.
35
Tell me about a time you used advanced SQL to solve a business problem.
Situation: the product team wanted to know where users dropped off in the onboarding flow. Task: build a funnel from raw event logs. Action: I used window functions to order events per user, LEAD to find the next step and time taken, and grouped the results into a funnel with conversion rates. Result: we found a 40 percent drop at KYC upload, and the redesign improved completion by 15 percent.
36
Produce region subtotals and a grand total in one query.
SELECT COALESCE(region, 'All Regions') AS region,
COALESCE(city, 'Subtotal') AS city,
SUM(sales) AS sales
FROM store_sales
GROUP BY ROLLUP(region, city)
ORDER BY region, city;
In MySQL use GROUP BY region, city WITH ROLLUP. Use GROUPING() to label totals when real NULLs exist.
37
Why is NOT IN dangerous when the subquery contains NULL?
If the subquery returns any NULL, value NOT IN (list) becomes unknown for every row, because comparing with NULL is unknown, so the query returns no rows. For example, NOT IN (SELECT manager_id FROM employees) fails when a manager_id is NULL. Use NOT EXISTS or filter out NULLs in the subquery to get correct results.
38
An e-commerce app needs products with very different attributes, such as phones and shirts. How do you model them?
Options are a common products table plus category-specific tables (table per type), an entity-attribute-value table, or a JSON or JSONB column for flexible attributes. A practical design keeps shared fields like name, price and category as columns, stores variable attributes in JSONB with indexes on frequently filtered keys, and validates required attributes in the application or with check constraints.
39
Find employees who earn more than their department average.
SELECT e.emp_id, e.name, e.department_id, e.salary
FROM employees e
WHERE e.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department_id = e.department_id
);
This is a correlated subquery. A window version uses AVG(salary) OVER (PARTITION BY department_id) in a CTE and filters in the outer query.
40
What is the difference between a unique index and a normal index?
A unique index enforces that no two rows have the same value in the indexed columns, and it is created automatically for primary keys and unique constraints. A normal index only speeds up lookups and allows duplicates. Both speed up searches, but a unique index also protects data integrity, for example preventing two users from registering with the same email.
Frequently asked questions
What is asked in SQL interviews for 2 years of experience?
At two years, interviews go deeper into window frames, recursive CTEs, query performance, execution plans and transactions. You will face scenario questions such as wrong totals after a join or a slow report. Interviewers expect you to reason about grain, data quality and trade-offs, not only syntax.
How many questions should I practise before a SQL interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 2 years of experience?
Focus first on Execution plans, Advanced window problems, Transactions, Debugging wrong results.