0% found this document useful (0 votes)
15 views6 pages

SQL Interview QA

This document is a comprehensive SQL interview preparation guide containing 50 questions and answers, categorized into easy, medium, and hard levels. It covers fundamental SQL queries, joins, grouping, filtering, window functions, and advanced topics like CTEs and recursive queries. Each question includes example SQL code and explanations to enhance understanding and practical application.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views6 pages

SQL Interview QA

This document is a comprehensive SQL interview preparation guide containing 50 questions and answers, categorized into easy, medium, and hard levels. It covers fundamental SQL queries, joins, grouping, filtering, window functions, and advanced topics like CTEs and recursive queries. Each question includes example SQL code and explanations to enhance understanding and practical application.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

SQL Interview — 50 Questions & Answers

Data Analytics Preparation Guide | Easy to Hard

■ EASY (Q1–Q15) — Fundamentals


Q1. Retrieve all columns from employees.
SELECT * FROM employees;

■ Use * to select all columns. Avoid in production; list columns explicitly.

Q2. Select only name and salary from employees.


SELECT name, salary FROM employees;

■ Always specify column names for clarity and performance.

Q3. Find employees with salary > 50000.


SELECT * FROM employees WHERE salary > 50000;

■ WHERE filters rows before returning results.

Q4. Retrieve employees from department 'HR'.


SELECT * FROM employees WHERE department = 'HR';

■ String values must be enclosed in single quotes.

Q5. Sort employees by salary descending.


SELECT * FROM employees ORDER BY salary DESC;

■ Use ASC for ascending (default), DESC for descending.

Q6. Find total number of employees.


SELECT COUNT(*) AS total_employees FROM employees;

■ COUNT(*) counts all rows including NULLs.

Q7. Get maximum salary.


SELECT MAX(salary) AS max_salary FROM employees;

■ Aggregate functions: MAX, MIN, SUM, AVG, COUNT.

Q8. Find minimum and average salary.


SELECT MIN(salary) AS min_salary, AVG(salary) AS avg_salary FROM employees;

■ Multiple aggregates can be used in one SELECT.

Q9. Retrieve distinct department names.


SELECT DISTINCT department FROM employees;

■ DISTINCT removes duplicate values from results.

Q10. Find employees whose name starts with 'A'.


SELECT * FROM employees WHERE name LIKE 'A%';

■ % = any number of chars, _ = exactly one char.


Q11. Get employees hired between 2020-01-01 and 2022-12-31.
SELECT * FROM employees WHERE hire_date BETWEEN '2020-01-01' AND '2022-12-31';

■ BETWEEN is inclusive on both ends.

Q12. Count employees in each department.


SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department;

■ GROUP BY groups rows; use with aggregate functions.

Q13. Find employees where email is NULL.


SELECT * FROM employees WHERE email IS NULL;

■ Never use = NULL. Always use IS NULL or IS NOT NULL.

Q14. Retrieve top 5 highest paid employees.


SELECT * FROM employees ORDER BY salary DESC LIMIT 5;

■ Use TOP 5 in SQL Server, ROWNUM in Oracle.

Q15. Show salary as monthly_pay using alias.


SELECT name, salary AS monthly_pay FROM employees;

■ AS keyword creates an alias. AS is optional but recommended.

■ MEDIUM (Q16–Q35) — Joins, Grouping & Filtering


Q16. INNER JOIN employees and departments on department_id.
SELECT [Link], d.department_name FROM employees e INNER JOIN departments d ON
e.department_id = d.department_id;

■ INNER JOIN returns only rows with matching keys in both tables.

Q17. Get all employees including those with no department (LEFT JOIN).
SELECT [Link], d.department_name FROM employees e LEFT JOIN departments d ON e.department_id
= d.department_id;

■ LEFT JOIN keeps all rows from left table; NULLs where no match.

Q18. Find departments that have no employees.


SELECT d.department_name FROM departments d LEFT JOIN employees e ON d.department_id =
e.department_id WHERE e.department_id IS NULL;

■ Filter with IS NULL after LEFT JOIN to find unmatched rows.

Q19. Get total salary spent per department.


SELECT department, SUM(salary) AS total_salary FROM employees GROUP BY department;

■ SUM with GROUP BY aggregates per group.

Q20. Find departments with average salary > 60000.


SELECT department, AVG(salary) AS avg_sal FROM employees GROUP BY department HAVING
AVG(salary) > 60000;

■ HAVING filters after GROUP BY. WHERE filters before grouping.


Q21. Find the second highest salary.
SELECT MAX(salary) AS second_highest FROM employees WHERE salary < (SELECT MAX(salary)
FROM employees);

■ Alternative: SELECT DISTINCT salary ORDER BY salary DESC LIMIT 1 OFFSET 1

Q22. Employees earning more than their department's average salary.


SELECT [Link], [Link], [Link] FROM employees e WHERE [Link] > ( SELECT
AVG(salary) FROM employees WHERE department = [Link] );

■ Correlated subquery: inner query references outer query's row.

Q23. Find duplicate email addresses.


SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;

■ HAVING COUNT(*) > 1 catches duplicates.

Q24. Customers with more than 3 orders.


SELECT customer_id, COUNT(*) AS order_count FROM orders GROUP BY customer_id HAVING COUNT(*)
> 3;

■ Common pattern: GROUP BY + HAVING for conditional aggregation.

Q25. Label employees as High/Medium/Low using CASE WHEN.


SELECT name, salary, CASE WHEN salary > 80000 THEN 'High' WHEN salary > 50000 THEN
'Medium' ELSE 'Low' END AS salary_band FROM employees;

■ CASE WHEN is SQL's if-else. Evaluated top to bottom.

Q26. Find employees who share salary with at least one other.
SELECT * FROM employees WHERE salary IN ( SELECT salary FROM employees GROUP BY salary
HAVING COUNT(*) > 1 );

■ IN with subquery is a clean way to filter matching sets.

Q27. Employees in the department with highest average salary.


SELECT * FROM employees WHERE department = ( SELECT department FROM employees GROUP BY
department ORDER BY AVG(salary) DESC LIMIT 1 );

■ Subquery first finds the department, outer query filters rows.

Q28. 3rd to 5th highest salaries.


SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 3 OFFSET 2;

■ OFFSET 2 skips the first 2 rows (ranks 1 and 2).

Q29. Join 3 tables: orders, customers, products.


SELECT [Link], o.order_id, p.product_name FROM orders o JOIN customers c ON o.customer_id =
c.customer_id JOIN products p ON o.product_id = p.product_id;

■ Chain multiple JOINs; each adds another table.

Q30. Customers who ordered in both 2023 and 2024.


SELECT customer_id FROM orders WHERE YEAR(order_date) = 2023 INTERSECT SELECT customer_id
FROM orders WHERE YEAR(order_date) = 2024;

■ INTERSECT returns rows common to both queries (MySQL: use subquery with IN).
Q31. Running total of sales ordered by date.
SELECT order_date, amount, SUM(amount) OVER ( ORDER BY order_date ) AS running_total FROM
orders;

■ SUM() OVER (ORDER BY ...) computes a cumulative sum.

Q32. Month-wise total revenue.


SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, SUM(amount) AS revenue FROM orders GROUP
BY DATE_FORMAT(order_date, '%Y-%m') ORDER BY month;

■ Use TO_CHAR in Oracle, FORMAT in SQL Server.

Q33. Find employees grouped by same manager_id.


SELECT manager_id, COUNT(*) AS direct_reports FROM employees WHERE manager_id IS NOT NULL
GROUP BY manager_id ORDER BY direct_reports DESC;

■ Useful for org-structure analysis.

Q34. Replace NULL bonus with 0 using COALESCE.


SELECT name, COALESCE(bonus, 0) AS bonus FROM employees;

■ COALESCE returns the first non-NULL value from the list.

Q35. Self-join: pairs of employees in the same department.


SELECT [Link] AS emp1, [Link] AS emp2, [Link] FROM employees a JOIN employees b ON
[Link] = [Link] AND a.employee_id < b.employee_id;

■ [Link] < [Link] prevents duplicate pairs and self-pairing.

■ HARD (Q36–Q50) — Window Functions, CTEs & Advanced


Q36. ROW_NUMBER() to rank employees by salary per department.
SELECT name, department, salary, ROW_NUMBER() OVER ( PARTITION BY department ORDER BY salary
DESC ) AS rn FROM employees;

■ ROW_NUMBER gives unique sequential ranks; no ties.

Q37. RANK() vs DENSE_RANK() — write both.


-- RANK: gaps after ties SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS rnk FROM
employees; -- DENSE_RANK: no gaps after ties SELECT name, salary, DENSE_RANK() OVER (ORDER
BY salary DESC) AS dense_rnk FROM employees;

■ RANK: 1,2,2,4 | DENSE_RANK: 1,2,2,3 — no gap after tie.

Q38. LEAD() and LAG() for month-over-month sales difference.


SELECT month, sales, LAG(sales) OVER (ORDER BY month) AS prev_month, LEAD(sales) OVER (ORDER
BY month) AS next_month, sales - LAG(sales) OVER (ORDER BY month) AS mom_diff FROM
monthly_sales;

■ LAG looks back; LEAD looks forward in the ordered window.

Q39. CTE to find top earner per department.


WITH ranked AS ( SELECT name, department, salary, RANK() OVER ( PARTITION BY department
ORDER BY salary DESC ) AS rnk FROM employees ) SELECT name, department, salary FROM ranked
WHERE rnk = 1;

■ CTEs (WITH clause) make complex queries more readable.


Q40. Recursive CTE for employee hierarchy.
WITH RECURSIVE emp_tree AS ( -- Anchor: top-level managers SELECT employee_id, name,
manager_id, 1 AS level FROM employees WHERE manager_id IS NULL UNION ALL -- Recursive:
subordinates SELECT e.employee_id, [Link], e.manager_id, [Link] + 1 FROM employees e JOIN
emp_tree et ON e.manager_id = et.employee_id ) SELECT * FROM emp_tree;

■ Recursive CTEs are ideal for tree/hierarchical data.

Q41. Cumulative sum of sales partitioned by region.


SELECT region, sale_date, amount, SUM(amount) OVER ( PARTITION BY region ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS cumulative_sales FROM sales;

■ PARTITION BY resets the running total for each region.

Q42. Customers who ordered every month in 2024.


SELECT customer_id FROM orders WHERE YEAR(order_date) = 2024 GROUP BY customer_id HAVING
COUNT(DISTINCT MONTH(order_date)) = 12;

■ COUNT DISTINCT months = 12 means every month was covered.

Q43. Detect gaps in sequential order IDs.


SELECT order_id + 1 AS gap_start FROM orders o WHERE NOT EXISTS ( SELECT 1 FROM orders WHERE
order_id = o.order_id + 1 ) AND order_id &lt; (SELECT MAX(order_id) FROM orders);

■ Alternative: use LAG to compare consecutive IDs.

Q44. Pivot: monthly sales as columns per product.


SELECT product_id, SUM(CASE WHEN MONTH(sale_date)=1 THEN amount ELSE 0 END) AS Jan, SUM(CASE
WHEN MONTH(sale_date)=2 THEN amount ELSE 0 END) AS Feb, SUM(CASE WHEN MONTH(sale_date)=3
THEN amount ELSE 0 END) AS Mar FROM sales GROUP BY product_id;

■ Conditional SUM with CASE WHEN is the standard pivot pattern.

Q45. First and last order date per customer using window functions.
SELECT DISTINCT customer_id, FIRST_VALUE(order_date) OVER ( PARTITION BY customer_id ORDER
BY order_date ) AS first_order, LAST_VALUE(order_date) OVER ( PARTITION BY customer_id ORDER
BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS last_order FROM
orders;

■ LAST_VALUE needs ROWS BETWEEN ... UNBOUNDED FOLLOWING to work correctly.

Q46. Month-over-month revenue growth %.


WITH monthly AS ( SELECT DATE_FORMAT(order_date,'%Y-%m') AS month, SUM(amount) AS revenue
FROM orders GROUP BY 1 ) SELECT month, revenue, LAG(revenue) OVER (ORDER BY month) AS
prev_revenue, ROUND( (revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER
(ORDER BY month) * 100, 2 ) AS growth_pct FROM monthly;

■ Combine CTE + LAG for clean MoM growth calculation.

Q47. Top 3 products by sales in each category.


WITH ranked AS ( SELECT category, product_id, SUM(amount) AS total_sales, DENSE_RANK() OVER
( PARTITION BY category ORDER BY SUM(amount) DESC ) AS rnk FROM sales GROUP BY category,
product_id ) SELECT category, product_id, total_sales FROM ranked WHERE rnk &lt;= 3;

■ DENSE_RANK ensures ties don't knock out a product from top 3.


Q48. Users who logged in on consecutive days.
WITH daily AS ( SELECT DISTINCT user_id, CAST(login_date AS DATE) AS dt, DATEADD(DAY,
-ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY login_date ), login_date) AS grp FROM
logins ) SELECT user_id, MIN(dt) AS streak_start, MAX(dt) AS streak_end, COUNT(*) AS
consecutive_days FROM daily GROUP BY user_id, grp HAVING COUNT(*) &gt;= 2;

■ Subtracting row number from date creates equal 'grp' for consecutive dates.

Q49. 7-day moving average of daily sales.


SELECT sale_date, daily_total, AVG(daily_total) OVER ( ORDER BY sale_date ROWS BETWEEN 6
PRECEDING AND CURRENT ROW ) AS moving_avg_7d FROM ( SELECT sale_date, SUM(amount) AS
daily_total FROM sales GROUP BY sale_date ) daily_sales;

■ ROWS BETWEEN 6 PRECEDING AND CURRENT ROW = 7-day window.

Q50. Find accounts where balance went negative using cumulative sum.
WITH running AS ( SELECT account_id, txn_date, amount, SUM(amount) OVER ( PARTITION BY
account_id ORDER BY txn_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS balance
FROM transactions ) SELECT DISTINCT account_id FROM running WHERE balance &lt; 0;

■ Cumulative SUM over ordered transactions simulates a running balance.

Best of luck with your Data Analytics Interview! ■ Practice each query on real data for best results.

You might also like