πŸŽ“ Velnov

πŸ—„οΈ 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.

Key concepts in this topic

Window functionsOVERPARTITION BYGROUP BYROW_NUMBERRankingRANKDENSE_RANKTop N per groupExecution orderRunning totalSUM OVERLAGLEADGrowthNTILEPercentilesFIRST_VALUELAST_VALUEFrameWindow frameMoving averageDefault behaviourDeduplication

Jump to: Medium Β· Hard

Medium questions (4)

Q18. medium

What is the first step to optimise a slow query?

  1. A Run EXPLAIN to see the execution plan and identify scans, missing indexes or bad join orders
  2. B Add more RAM
  3. C Rewrite it randomly
  4. D Drop the table

Answer: A β€” Run EXPLAIN to see the execution plan and identify scans, missing indexes or bad join orders

Measure before changing. EXPLAIN (or EXPLAIN ANALYZE) shows exactly how the database runs the query, so you optimise the real bottleneck rather than guessing.

EXPLAINOptimisation

Q20. medium

Why avoid SELECT * in production queries?

  1. A It transfers unneeded columns, prevents covering indexes, and breaks if the schema changes
  2. B It is a syntax error
  3. C It is always faster
  4. D No reason

Answer: A β€” It transfers unneeded columns, prevents covering indexes, and breaks if the schema changes

Selecting only needed columns reduces I/O and network transfer and can let an index satisfy the query entirely (covering index). It also makes code resilient to column changes.

SELECT *Performance

Q24. medium

Why keep table statistics up to date?

  1. A The optimiser uses them to estimate row counts and choose good plans; stale stats lead to bad plans
  2. B They store backups
  3. C They are decorative
  4. D They index data

Answer: A β€” The optimiser uses them to estimate row counts and choose good plans; stale stats lead to bad plans

After large data changes, running ANALYZE (or UPDATE STATISTICS) refreshes the numbers the optimiser relies on, helping it pick the right indexes and join methods.

StatisticsOptimiser

Q28. medium

What is denormalization's role in read optimization?

  1. A Pre-joining or pre-aggregating data reduces expensive joins at read time, at the cost of update complexity
  2. B It always slows reads
  3. C It removes all indexes
  4. D It has no effect

Answer: A β€” Pre-joining or pre-aggregating data reduces expensive joins at read time, at the cost of update complexity

For analytics dashboards, storing a summary table (updated periodically) avoids re-joining and re-aggregating huge tables on every query - a deliberate performance trade-off.

DenormalizationRead optimisation

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;
  1. A A function that computes across a set of rows RELATED to the current row, without collapsing them into one
  2. B A GUI feature
  3. C The same as GROUP BY
  4. 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?

  1. A The 'window' of rows the function operates on - via PARTITION BY and ORDER BY
  2. B Which table to use
  3. C The output format
  4. 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?

  1. A GROUP BY collapses rows into one per group; a window function keeps all rows and adds the computed value to each
  2. B They are identical
  3. C Window functions delete rows
  4. 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?

  1. A Divides rows into groups; the function restarts its calculation for each partition
  2. B Sorts the rows
  3. C Filters rows
  4. 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?

  1. A A sequential number 1, 2, 3... assigned by descending marks - unique even for ties
  2. B The same number for ties
  3. C The marks value
  4. 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;
  1. A RANK leaves gaps after ties (1,1,3); DENSE_RANK does not (1,1,2)
  2. B They are identical
  3. C DENSE_RANK leaves gaps
  4. 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;
  1. A Use ROW_NUMBER/RANK OVER (PARTITION BY dept ORDER BY salary DESC) and filter <= 3 in an outer query
  2. B ORDER BY salary LIMIT 3
  3. C GROUP BY dept HAVING salary
  4. 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?

  1. A Window functions are computed AFTER WHERE (during SELECT), so the value does not exist yet - use a subquery/CTE
  2. B It is a syntax preference
  3. C You can filter them in WHERE
  4. 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;
  1. A SUM(amount) OVER (ORDER BY date) - accumulates as rows progress
  2. B SUM(amount) GROUP BY date
  3. C SUM(amount) OVER () only
  4. 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;
  1. A Access a value from a PREVIOUS (LAG) or NEXT (LEAD) row - useful for period-over-period comparisons
  2. B Sort rows
  3. C Lag the query
  4. 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?

  1. A sales - LAG(sales) OVER (ORDER BY month) - compare each row to the previous
  2. B SUM(sales) GROUP BY month
  3. C MAX(sales) - MIN(sales)
  4. 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?

  1. A Divides ordered rows into 4 roughly equal buckets (quartiles), labelling each row 1-4
  2. B Returns the 4th row
  3. C Multiplies by 4
  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?

  1. A The first or last value in the window's ordered frame - e.g. the earliest order per customer
  2. B The min and max
  3. C The row count
  4. 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)
  1. A It defines which rows around the current row are included - e.g. a 3-row moving average
  2. B A picture frame
  3. C The whole table always
  4. 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?

  1. A RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - a running total up to the current row (including ties)
  2. B The whole partition
  3. C Only the current row
  4. 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?

  1. A ROW_NUMBER() OVER (PARTITION BY key ORDER BY created_at DESC) then keep rows where the number = 1
  2. B DISTINCT key
  3. C GROUP BY key
  4. 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;
  1. A The grand total of x across ALL rows, repeated on every row
  2. B A running total
  3. C The current row only
  4. 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?

  1. A Adding an index on the columns used in WHERE/JOIN, and making conditions sargable
  2. B Using SELECT *
  3. C Removing WHERE clauses
  4. 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?

  1. A With a matching index and ORDER BY, the database can stop after finding the first N rows instead of sorting everything
  2. B It never helps
  3. C It scans twice
  4. 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?

  1. A Running one query per row of a previous result (in application code) instead of a single join - very slow
  2. B A window function
  3. C An index type
  4. 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?

  1. A When the condition wraps the column in a function, uses a leading wildcard LIKE '%x', or the query returns most rows
  2. B Never - indexes always apply
  3. C Only on small tables
  4. 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?

  1. A If materialised, it may prevent the optimiser from pushing filters down or using indexes across the boundary
  2. B They always speed things up
  3. C They cannot affect performance
  4. 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?

  1. A Per-group ordered calculations - e.g. running total or rank that restarts for each partition
  2. B A single total
  3. C Random ordering
  4. 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?

  1. A AVG(value) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
  2. B AVG(value) GROUP BY date
  3. C AVG(value) OVER ()
  4. 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?

  1. A DENSE_RANK() OVER (ORDER BY value DESC) and filter for the rank = N
  2. B ORDER BY value LIMIT N
  3. C MAX(value)
  4. 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?

  1. A Measure with EXPLAIN, fix the real bottleneck (usually indexing and reducing rows early), then re-measure
  2. B Guess and add random hints
  3. C Always rewrite as subqueries
  4. 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

Practice β€œWindow Functions & Optimization” as a quiz