There are 3 window functions for ranking, and they handle ties differently. Because the right choice depends on how well it fits the business requirement, it is important to understand the differences precisely.
ROW_NUMBER() OVER (ORDER BY col) -- Always assigns a sequence, even for ties. The ORDER BY sequence matters RANK() OVER (ORDER BY col) -- Ties receive the same rank. The next rank skips by the number of tied rows DENSE_RANK() OVER (ORDER BY col) -- Ties receive the same rank. The next rank is always +1 (no gaps)
From the score_board table, assign each player 3 rankings—ROW_NUMBER, RANK, and DENSE_RANK—in descending score order. Return the columns player_id, score, row_num, rank, dense_rank, ordered by score descending and player_id ascending.
| player_id | score |
|---|---|
| P1 | 980 |
| P2 | 750 |
| P3 | 980 |
| P4 | 620 |
| P5 | 750 |
| P6 | 880 |
Expected output (score descending, player_id ascending):
| player_id | score | row_num | rank | dense_rank |
|---|---|---|---|---|
| P1 | 980 | 1 | 1 | 1 |
| P3 | 980 | 2 | 1 | 1 |
| P6 | 880 | 3 | 3 | 2 |
| P2 | 750 | 4 | 4 | 3 |
| P5 | 750 | 5 | 4 | 3 |
| P4 | 620 | 6 | 6 | 4 |
P1 and P3 are tied at 980: RANK → both 1st (skips to 3rd next) / DENSE_RANK → both 1st (2nd next) / ROW_NUMBER → sequential 1, 2 (determined by ascending player_id)
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
LAG / LEAD are window functions that reference the value N rows before or after the current row. They calculate differences from the previous or next period without a self-join through GROUP BY.
LAG(col, 1) OVER (ORDER BY date_col) -- Value 1 row earlier (default offset=1) LAG(col, 3) OVER (ORDER BY date_col) -- 3 rows earlier (for quarter-over-quarter comparisons, for example) LEAD(col, 1) OVER (ORDER BY date_col) -- Value 1 row later -- Prevent division by zero: NULLIF(val, 0) → returns NULL when val is 0 NULLIF(prev_revenue, 0) -- Returns NULL instead of dividing by 0 (essential in production)
LAG(col, 1, 0).From the monthly_revenue table, calculate monthly revenue, previous-month revenue, and month-over-month growth rate (%). Return the columns month, revenue, prev_revenue, growth_rate_pct (round the growth rate to 2 decimal places), ordered by month ascending. For the first month, both prev_revenue and growth_rate_pct are NULL.
| month | revenue |
|---|---|
| 2024-01 | 1000000 |
| 2024-02 | 1200000 |
| 2024-03 | 1100000 |
| 2024-04 | 1350000 |
| 2024-05 | 1500000 |
| 2024-06 | 1420000 |
Expected output (month ascending):
| month | revenue | prev_revenue | growth_rate_pct |
|---|---|---|---|
| 2024-01 | 1000000 | NULL | NULL |
| 2024-02 | 1200000 | 1000000 | 20.00 |
| 2024-03 | 1100000 | 1200000 | -8.33 |
| 2024-04 | 1350000 | 1100000 | 22.73 |
| 2024-05 | 1500000 | 1350000 | 11.11 |
| 2024-06 | 1420000 | 1500000 | -5.33 |
Growth rate = (current month − previous month) / previous month × 100. 2024-03 is -8.33% versus the previous month's 1,200,000 (down 100,000) / 2024-06 is -5.33% month over month (down 80,000)
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
A running total is the sum accumulated from the first row through the current row, implemented with a window-frame specification. Combining it with a cumulative ratio (%) lets you check the 80:20 rule (the Pareto principle) in a single query.
SUM(col) OVER ( ORDER BY col DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- Sum from the first row through the current row ) → Running total for each row SUM(col) OVER () → Grand total of all rows (without PARTITION BY)
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (value-based). When multiple rows have the same value, RANGE includes all tied rows in the frame, which changes the running-total result. Always specify ROWS BETWEEN for a running total.From the category_sales table, calculate a running total and cumulative ratio (%) in descending sales order. Calculate the running total and grand total in a CTE, then calculate the ratio in the outer query. Return category, sales_amount, cum_sales, cum_pct (round cum_pct to 2 decimal places), ordered by sales_amount descending.
| category | sales_amount |
|---|---|
| Electronics | 4800000 |
| Apparel | 3200000 |
| Food | 2100000 |
| Books | 1400000 |
| Others | 900000 |
Expected output (sales_amount descending):
| category | sales_amount | cum_sales | cum_pct |
|---|---|---|---|
| Electronics | 4800000 | 4800000 | 38.71 |
| Apparel | 3200000 | 8000000 | 64.52 |
| Food | 2100000 | 10100000 | 81.45 |
| Books | 1400000 | 11500000 | 92.74 |
| Others | 900000 | 12400000 | 100.00 |
Grand total=12,400,000 / Electronics + Apparel = 64.52%, so the top 2 categories account for two-thirds of sales. Including Food, the top 3 categories account for 81.45% (a distribution close to the Pareto principle).
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
NTILE(n) assigns each row a bucket number (1–n) after ordering the rows with ORDER BY. It divides the rows into n groups by frequency, which is fundamentally different from CASE WHEN with fixed thresholds.
NTILE(4) OVER (ORDER BY col DESC) -- Order all rows from high to low and divide them into 4 groups. If the row count is not a multiple of 4, distribute the remainder to the higher buckets -- Example: divide 9 rows by 4 → bucket 1 has 3 rows, buckets 2–4 have 2 rows each ← distribute the remainder one row at a time from the front -- Difference from WIDTH_BUCKET (see Basic Q4) WIDTH_BUCKET: equal-width buckets (intervals have the same width; row counts may be uneven) NTILE: equal-frequency buckets (row counts are even; interval widths may be uneven)
Classify purchase amounts in the user_spending table into quartiles with NTILE(4) in descending order, and assign the segment labels VIP / Gold / Silver / Bronze. Return user_id, total_spend, quartile, segment, ordered by total_spend descending.
| user_id | total_spend |
|---|---|
| U01 | 15000 |
| U02 | 8500 |
| U03 | 52000 |
| U04 | 3200 |
| U05 | 28000 |
| U06 | 6400 |
| U07 | 41000 |
| U08 | 1800 |
Expected output (total_spend descending):
| user_id | total_spend | quartile | segment |
|---|---|---|---|
| U03 | 52000 | 1 | VIP |
| U07 | 41000 | 1 | VIP |
| U05 | 28000 | 2 | Gold |
| U01 | 15000 | 2 | Gold |
| U02 | 8500 | 3 | Silver |
| U06 | 6400 | 3 | Silver |
| U04 | 3200 | 4 | Bronze |
| U08 | 1800 | 4 | Bronze |
8 rows divided into 4 equal groups → 2 rows per bucket. VIP: U03(52000)/U07(41000) / Gold: U05(28000)/U01(15000) / Silver: U02(8500)/U06(6400) / Bronze: U04(3200)/U08(1800)
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free
The correlation coefficient (CORR) expresses the strength of a linear relationship between 2 variables as a value from -1 to 1. PostgreSQL's CORR(y, x) function returns the sample correlation coefficient.
CORR(y, x) -- Sample correlation coefficient (-1 to 1): strength and direction of a linear relationship COVAR_SAMP(y, x) -- Sample covariance (divided by n-1): “corresponds to the numerator” of CORR COVAR_POP(y, x) -- Population covariance (divided by n) -- Internal calculation of CORR: -- CORR(y, x) = COVAR_SAMP(y, x) / (STDDEV_SAMP(x) * STDDEV_SAMP(y))
From the ad_performance table, calculate the correlation coefficient between ad spend (ad_spend) and revenue, each mean, and the number of data points in 1 row. Return corr_spend_revenue, avg_spend, avg_revenue, data_points (round the correlation coefficient to 2 decimal places and each mean to an integer).
| channel | ad_spend | revenue |
|---|---|---|
| Search | 500000 | 2000000 |
| Social | 300000 | 2000000 |
| Display | 100000 | 500000 |
| Video | 400000 | 1800000 |
| 200000 | 700000 |
Expected output (1 row):
| corr_spend_revenue | avg_spend | avg_revenue | data_points |
|---|---|---|---|
| 0.88 | 300000 | 1400000 | 5 |
CORR ≈ 0.879 (strong positive correlation) / avg_spend: (500K+300K+100K+400K+200K)/5=300K / avg_revenue: (2000K+2000K+500K+1800K+700K)/5=1400K / Note: Social has revenue=2000K with ad spend=300K (higher ROI than the others)
- Read every model answer, explanation and table-transition visualization across all 48 advanced sets (240 questions)
- One-time purchase — no subscription. Future advanced sets are included
- Background, problem and expected output stay free