ποΈ SQL Β· Topic 20 of 20
Window Functions & Optimization β SQL Questions with Answers
30 practice questions on Window Functions & Optimization, each with the correct answer and a clear explanation of why it is right. Read through them as revision notes, or take this topic as an interactive quiz to test yourself.
0 easy Β· 4 medium Β· 26 hard Β· Part of SQL: Databases from basics to window functions - 20 topics.
Hard questions (26)
Q1. hard
What is a window function?
SELECT name, dept, salary,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg
FROM employees;
- A A function that computes across a set of rows RELATED to the current row, without collapsing them into one
- B A GUI feature
- C The same as GROUP BY
- D A type of index
Answer: A β A function that computes across a set of rows RELATED to the current row, without collapsing them into one
Unlike aggregates with GROUP BY (which return one row per group), window functions add a computed column while KEEPING every row - perfect for running totals, ranks and comparisons to a group.
Window functionsOVER
Q2. hard
What does the OVER() clause define?
- A The 'window' of rows the function operates on - via PARTITION BY and ORDER BY
- B Which table to use
- C The output format
- D An index
Answer: A β The 'window' of rows the function operates on - via PARTITION BY and ORDER BY
OVER() is what makes a function a window function. Empty OVER() means the whole result set; PARTITION BY splits it into groups; ORDER BY orders rows within each partition.
OVERPARTITION BY
Q3. hard
What is the difference between GROUP BY and a window function?
- A GROUP BY collapses rows into one per group; a window function keeps all rows and adds the computed value to each
- B They are identical
- C Window functions delete rows
- D GROUP BY keeps all rows
Answer: A β GROUP BY collapses rows into one per group; a window function keeps all rows and adds the computed value to each
To show each employee's salary alongside their department average WITHOUT losing individual rows, you need a window function. GROUP BY would leave you with one row per department.
Window functionsGROUP BY
Q4. hard
What does PARTITION BY do in a window function?
- A Divides rows into groups; the function restarts its calculation for each partition
- B Sorts the rows
- C Filters rows
- D Creates partitions on disk
Answer: A β Divides rows into groups; the function restarts its calculation for each partition
PARTITION BY dept means the window function (rank, running total, average) computes separately within each department, like a GROUP BY that keeps every row.
PARTITION BYOVER
Q5. hard
What does ROW_NUMBER() OVER (ORDER BY marks DESC) produce?
- A A sequential number 1, 2, 3... assigned by descending marks - unique even for ties
- B The same number for ties
- C The marks value
- D A random number
Answer: A β A sequential number 1, 2, 3... assigned by descending marks - unique even for ties
ROW_NUMBER always gives distinct consecutive numbers, breaking ties arbitrarily. It is the tool for 'give me the top N per group' and for de-duplication.
ROW_NUMBERRanking
Q6. hard
What is the difference between RANK() and DENSE_RANK()?
SELECT name, RANK() OVER (ORDER BY marks DESC) AS r,
DENSE_RANK() OVER (ORDER BY marks DESC) AS dr
FROM students;
- A RANK leaves gaps after ties (1,1,3); DENSE_RANK does not (1,1,2)
- B They are identical
- C DENSE_RANK leaves gaps
- D RANK never allows ties
Answer: A β RANK leaves gaps after ties (1,1,3); DENSE_RANK does not (1,1,2)
With two rows tied at rank 1, RANK gives the next row rank 3 (skipping 2), while DENSE_RANK gives it rank 2. ROW_NUMBER would give 1 and 2 to the tied rows.
RANKDENSE_RANK
Q7. hard
How do you find the top 3 earners PER DEPARTMENT?
WITH r AS (
SELECT *, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) rk FROM emp
)
SELECT * FROM r WHERE rk <= 3;
- A Use ROW_NUMBER/RANK OVER (PARTITION BY dept ORDER BY salary DESC) and filter <= 3 in an outer query
- B ORDER BY salary LIMIT 3
- C GROUP BY dept HAVING salary
- D MAX(salary)
Answer: A β Use ROW_NUMBER/RANK OVER (PARTITION BY dept ORDER BY salary DESC) and filter <= 3 in an outer query
You cannot filter a window function in WHERE (it is computed after WHERE), so wrap it in a subquery/CTE and filter the rank there. This 'top N per group' is a classic window-function use.
Top N per groupPARTITION BY
Q8. hard
Why can't you filter a window function's result in WHERE?
- A Window functions are computed AFTER WHERE (during SELECT), so the value does not exist yet - use a subquery/CTE
- B It is a syntax preference
- C You can filter them in WHERE
- D They run first
Answer: A β Window functions are computed AFTER WHERE (during SELECT), so the value does not exist yet - use a subquery/CTE
Execution order places window functions after WHERE and GROUP BY. To filter on rank or a running total, compute it in a CTE and apply WHERE in the outer query.
Execution orderWindow functions
Q9. hard
What does a running total look like with window functions?
SELECT date, amount,
SUM(amount) OVER (ORDER BY date) AS running_total
FROM transactions;
- A SUM(amount) OVER (ORDER BY date) - accumulates as rows progress
- B SUM(amount) GROUP BY date
- C SUM(amount) OVER () only
- D It is impossible
Answer: A β SUM(amount) OVER (ORDER BY date) - accumulates as rows progress
Adding ORDER BY to a windowed SUM makes it a running total (cumulative sum) up to the current row. PARTITION BY resets it per group (e.g. running total per account).
Running totalSUM OVER
Q10. hard
What do LAG() and LEAD() do?
SELECT month, sales,
sales - LAG(sales) OVER (ORDER BY month) AS change
FROM monthly;
- A Access a value from a PREVIOUS (LAG) or NEXT (LEAD) row - useful for period-over-period comparisons
- B Sort rows
- C Lag the query
- D Delete rows
Answer: A β Access a value from a PREVIOUS (LAG) or NEXT (LEAD) row - useful for period-over-period comparisons
LAG(sales) OVER (ORDER BY month) gives last month's sales on the current row, so you can compute growth. LEAD looks forward. Both replace awkward self joins.
LAGLEAD
Q11. hard
How do you compute month-over-month growth?
- A sales - LAG(sales) OVER (ORDER BY month) - compare each row to the previous
- B SUM(sales) GROUP BY month
- C MAX(sales) - MIN(sales)
- D It cannot be done
Answer: A β sales - LAG(sales) OVER (ORDER BY month) - compare each row to the previous
LAG pulls the prior month's value into the current row, so a simple subtraction gives the change. Divide by the lagged value for a percentage.
LAGGrowth
Q12. hard
What does NTILE(4) do?
- A Divides ordered rows into 4 roughly equal buckets (quartiles), labelling each row 1-4
- B Returns the 4th row
- C Multiplies by 4
- D Creates 4 tables
Answer: A β Divides ordered rows into 4 roughly equal buckets (quartiles), labelling each row 1-4
NTILE distributes rows into N groups - useful for quartiles, percentiles and cohort analysis. NTILE(100) approximates percentiles.
NTILEPercentiles
Q13. hard
What do FIRST_VALUE() and LAST_VALUE() return?
- A The first or last value in the window's ordered frame - e.g. the earliest order per customer
- B The min and max
- C The row count
- D The current row
Answer: A β The first or last value in the window's ordered frame - e.g. the earliest order per customer
FIRST_VALUE(price) OVER (PARTITION BY product ORDER BY date) gives the earliest price on every row. LAST_VALUE needs a full frame clause to work as expected, a common gotcha.
FIRST_VALUELAST_VALUEFrame
Q14. hard
What is a window FRAME (ROWS BETWEEN ...)?
AVG(price) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
- A It defines which rows around the current row are included - e.g. a 3-row moving average
- B A picture frame
- C The whole table always
- D An index
Answer: A β It defines which rows around the current row are included - e.g. a 3-row moving average
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW creates a moving window. Frames enable moving averages and custom cumulative ranges - a powerful, advanced feature.
Window frameMoving average
Q15. hard
What is the default frame when you use ORDER BY in OVER() without ROWS?
- A RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - a running total up to the current row (including ties)
- B The whole partition
- C Only the current row
- D No frame
Answer: A β RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - a running total up to the current row (including ties)
This default is why SUM(...) OVER (ORDER BY x) is a running total. It also explains the LAST_VALUE surprise: the default frame ends at the current row, not the partition end.
FrameDefault behaviour
Q16. hard
How do you deduplicate keeping the newest row per key?
- A ROW_NUMBER() OVER (PARTITION BY key ORDER BY created_at DESC) then keep rows where the number = 1
- B DISTINCT key
- C GROUP BY key
- D MAX(created_at)
Answer: A β ROW_NUMBER() OVER (PARTITION BY key ORDER BY created_at DESC) then keep rows where the number = 1
Numbering rows per key by recency and keeping rn = 1 selects the latest per group while retaining its other columns - which DISTINCT and GROUP BY cannot do cleanly.
ROW_NUMBERDeduplication
Q17. hard
What does SUM(x) OVER () (empty parentheses) compute?
SELECT amount, amount * 100.0 / SUM(amount) OVER () AS pct FROM sales;
- A The grand total of x across ALL rows, repeated on every row
- B A running total
- C The current row only
- D An error
Answer: A β The grand total of x across ALL rows, repeated on every row
Empty OVER() treats the whole result set as one window. It is the neat way to add a grand total to every row - handy for computing each row's percentage of the total.
OVER()Percent of total
Q19. hard
Which query change most often gives the biggest speed-up?
- A Adding an index on the columns used in WHERE/JOIN, and making conditions sargable
- B Using SELECT *
- C Removing WHERE clauses
- D Adding more subqueries
Answer: A β Adding an index on the columns used in WHERE/JOIN, and making conditions sargable
A missing index turning a full scan into an index seek can be orders of magnitude faster. Ensure conditions do not wrap the column in a function, or the index cannot be used.
IndexesSargability
Q21. hard
How does LIMIT help performance with an index?
- A With a matching index and ORDER BY, the database can stop after finding the first N rows instead of sorting everything
- B It never helps
- C It scans twice
- D It ignores indexes
Answer: A β With a matching index and ORDER BY, the database can stop after finding the first N rows instead of sorting everything
An index that already provides the requested order lets the engine read just the first N rows. Without the index it may sort the entire result before applying LIMIT.
LIMITIndexes
Q22. hard
What is the N+1 query problem?
- A Running one query per row of a previous result (in application code) instead of a single join - very slow
- B A window function
- C An index type
- D A transaction level
Answer: A β Running one query per row of a previous result (in application code) instead of a single join - very slow
Fetching 100 orders then querying each order's customer separately = 101 queries. A single JOIN (or IN clause) does it in one round trip. A common ORM performance trap.
N+1 problemJoins
Q23. hard
When does an index NOT get used despite existing?
- A When the condition wraps the column in a function, uses a leading wildcard LIKE '%x', or the query returns most rows
- B Never - indexes always apply
- C Only on small tables
- D When you use WHERE
Answer: A β When the condition wraps the column in a function, uses a leading wildcard LIKE '%x', or the query returns most rows
Non-sargable conditions (functions on the column), leading-wildcard searches, and low-selectivity queries all defeat indexes. EXPLAIN reveals when a scan is chosen instead.
SargabilityIndex usage
Q25. hard
How can a CTE or subquery sometimes hurt performance?
- A If materialised, it may prevent the optimiser from pushing filters down or using indexes across the boundary
- B They always speed things up
- C They cannot affect performance
- D They add indexes
Answer: A β If materialised, it may prevent the optimiser from pushing filters down or using indexes across the boundary
In some databases a CTE is an optimisation fence. Readability is valuable, but for hot queries check EXPLAIN - occasionally inlining the logic lets the optimiser do more.
CTEOptimisation
Q26. hard
What does PARTITION BY combined with ORDER BY in a window give you?
- A Per-group ordered calculations - e.g. running total or rank that restarts for each partition
- B A single total
- C Random ordering
- D An error
Answer: A β Per-group ordered calculations - e.g. running total or rank that restarts for each partition
PARTITION BY account ORDER BY date with SUM gives a running balance PER account. The partition resets the calculation; the order defines the accumulation direction.
PARTITION BYRunning total
Q27. hard
How do you compute a 7-day moving average?
- A AVG(value) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
- B AVG(value) GROUP BY date
- C AVG(value) OVER ()
- D It is impossible
Answer: A β AVG(value) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
The frame ROWS BETWEEN 6 PRECEDING AND CURRENT ROW includes the current day plus the six before it - a 7-row moving average. Frames make time-series smoothing straightforward.
Moving averageWindow frame
Q29. hard
How do you find the Nth highest value robustly?
- A DENSE_RANK() OVER (ORDER BY value DESC) and filter for the rank = N
- B ORDER BY value LIMIT N
- C MAX(value)
- D It is impossible
Answer: A β DENSE_RANK() OVER (ORDER BY value DESC) and filter for the rank = N
DENSE_RANK handles ties sensibly and generalises to any N, unlike nested MAX subqueries or LIMIT/OFFSET tricks. Filter the ranked result in an outer query.
DENSE_RANKNth highest
Q30. hard
What is the single best mindset for query optimization?
- A Measure with EXPLAIN, fix the real bottleneck (usually indexing and reducing rows early), then re-measure
- B Guess and add random hints
- C Always rewrite as subqueries
- D Add every possible index
Answer: A β Measure with EXPLAIN, fix the real bottleneck (usually indexing and reducing rows early), then re-measure
Optimisation is empirical: profile the actual plan, address the biggest cost (a missing index, a full scan, fetching too much), and verify the improvement. Avoid premature or blind tuning.
OptimisationEXPLAIN