<DataInsights />
  • ๐Ÿ  Home
  • ๐Ÿ“Š SQL
  • ๐Ÿ Python
  • ๐Ÿ“ˆ Power BI
  • ๐Ÿ“— Excel
  • ๐Ÿ’ผ Career
  • ๐ŸŽฏ Interview Q&A
  • ๐Ÿ“ Case Study
  • ๐Ÿ“ฅ Downloads
  • ๐Ÿš€ My Portfolio
<DataInsights />

Practical Data Analytics tutorials covering SQL, Python, Power BI, Excel and career guidance for aspiring analysts โ€” 100% free.

Topics

  • SQL Tutorials
  • Python Guide
  • Power BI
  • Excel Tips
  • Career Guide

Quick Links

  • ๐Ÿ› ๏ธ All Tools
  • ๐Ÿ—“๏ธ Archive
  • ๐Ÿ“ฌ Contact
  • ๐Ÿ” Search
  • Portfolio
  • Kaggle
  • GitHub

Legal & Info

  • About
  • Contact
  • Privacy Policy
  • Disclaimer
  • Terms & Conditions
  • DMCA
  • Sitemap
Copyright ยฉ 2026 Data Insights by Jatin Kumar. All Rights Reserved.Built with โค๏ธ for Data Analysts
Home/SQL/SQL Window Functions โ€” Complete Guide...

SQL Window Functions โ€” Complete Guide

A
August 6, 2026 Jatin Kumar 34 min read SQL
Data Insights SQL Topic Wise

SQL Window Functions โ€” Complete Guide ๐ŸชŸ

Window Functions โ€” modern SQL ka superpower. Ranking, running totals, moving averages, previous/next row access โ€” sab kuch bina GROUP BY ke, rows retain karke. Basic OVER() se lekar advanced frames tak โ€” professional queries ke saath complete deep dive. Data Insights par.

๐Ÿ“‘ Is Chapter Mein Kya Sikhenge:

  • ๐ŸŸข Foundation: Introduction, OVER() clause, Sample Dataset
  • ๐ŸŸก Ranking Functions: ROW_NUMBER, RANK, DENSE_RANK, NTILE
  • ๐Ÿ”ด Advanced (Part B): LAG/LEAD, FIRST_VALUE/LAST_VALUE, Aggregate Windows, Frames, Real Scenarios

Topic 1: Window Functions Kya Hain โ€” Introduction

๐Ÿ“˜ Definition: Window Functions perform calculations across a set of table rows that are related to the current row โ€” called a "window." Unlike GROUP BY which collapses rows into groups, Window Functions retain ALL individual rows while adding calculated values as new columns. They use the OVER() clause to define the window (subset of rows to operate on).

๐ŸŽฏ Explanation: GROUP BY mein 100 rows โ†’ 5 groups (rows collapse ho jaati hain โ€” details lose). Window Functions mein 100 rows โ†’ 100 rows PLUS har row ke saath ek extra calculated column. Rows retain hoti hain! Example: "Har employee ke saath uske department ki average salary bhi dikhao" โ€” GROUP BY se nahi ho sakta (rows collapse hongi), Window Function se easily ho jaayega. Yeh modern SQL ka most powerful feature hai โ€” data analysis mein bahut use hota hai.

๐Ÿ“‹ GROUP BY vs Window Function โ€” Key Difference:

Feature GROUP BY Window Function
Rows Output Collapses โ€” one row per group Retains โ€” all individual rows
Detail Level Summary only Row detail + summary together
Use Case Aggregated reports Row-level analytics with context
Example "Total sales per region" (5 rows) "Each sale + its region's total" (100 rows)

๐Ÿ“‹ Types of Window Functions:

3 Categories of Window Functions:
RANKING FUNCTIONS
โ€ข ROW_NUMBER() โ†’ unique sequential numbers
โ€ข RANK() โ†’ gaps in ties (1, 2, 2, 4)
โ€ข DENSE_RANK() โ†’ no gaps (1, 2, 2, 3)
โ€ข NTILE(n) โ†’ divide into n buckets

VALUE FUNCTIONS
โ€ข LAG(col) โ†’ previous row's value
โ€ข LEAD(col) โ†’ next row's value
โ€ข FIRST_VALUE(col) โ†’ first value in window
โ€ข LAST_VALUE(col) โ†’ last value in window
โ€ข NTH_VALUE(col,n) โ†’ Nth value in window

AGGREGATE WINDOW FUNCTIONS
โ€ข SUM() OVER() โ†’ running total
โ€ข AVG() OVER() โ†’ moving average
โ€ข COUNT() OVER() โ†’ cumulative count
โ€ข MIN/MAX OVER() โ†’ running min/max

โš ๏ธ Common Mistake: Window functions ko GROUP BY ke saath confuse karna. GROUP BY rows COLLAPSE karta hai โ€” details khatam. Window function rows RETAIN karta hai โ€” details ke saath aggregate. Interview mein pehla question โ€” "difference between GROUP BY and Window Function?"

Topic 2: OVER() Clause โ€” The Heart of Window Functions

๐Ÿ“˜ Definition: The OVER() clause defines the "window" โ€” the set of rows the function operates on. Without OVER(), any function becomes a regular function or aggregate. OVER() can be empty (entire result set as window) or contain PARTITION BY (divide into groups) and ORDER BY (sort within groups). This is the CORE of window functions.

๐ŸŽฏ Explanation: OVER() = "window kya hai?" batata hai. 3 modes hain: (1) OVER() empty โ€” poora result set ek window. (2) OVER(PARTITION BY col) โ€” groups mein baanto (GROUP BY jaisa but rows retain). (3) OVER(ORDER BY col) โ€” sort karo, running calculations enable karo. Aur dono combine kar sakte ho. Yeh OVER() clause samajh gaye toh window functions ka 70% kaam ho gaya!

๐Ÿ“‹ OVER() Clause Syntax:

Basic Syntax:
window_function() OVER (
[PARTITION BY column1, column2, ...]
[ORDER BY column3 [ASC|DESC], ...]
[frame_clause]
)

Parts Explained:
โ€ข PARTITION BY โ†’ divide rows into groups (like GROUP BY but retains rows)
โ€ข
ORDER BY โ†’ sort rows within each partition
โ€ข frame_clause โ†’ subset of window (advanced โ€” covered later)

3 Common Patterns:

Pattern 1 โ€” Entire table as window:
SUM(amount) OVER ()

Pattern 2 โ€”
Group by region:
SUM(amount) OVER (PARTITION BY region)

Pattern 3 โ€” Running total by date:
SUM(amount) OVER (
PARTITION BY region
ORDER BY sale_date
)

๐Ÿ’ป Basic Examples โ€” Understanding OVER():

-- Example 1: OVER() empty โ€” entire result set as window
SELECT product_name, amount, SUM(amount) OVER () AS total_sales,
AVG(amount) OVER () AS avg_sales
FROM sales;
product_nameamounttotal_salesavg_sales
Laptop5500024500049000
Mouse200024500049000
Keyboard350024500049000
Monitor1800024500049000
Chair800024500049000

Same total in every row! All rows retained + summary added to each row!

-- Example 2: OVER(PARTITION BY) โ€” grouped windows
SELECT product_name, category, amount, SUM(amount) OVER ( PARTITION BY category ) AS category_total
FROM sales;
product_namecategoryamountcategory_total
ChairFurniture80008000
LaptopElectronics5500073000
MonitorElectronics1800073000
KeyboardAccessories35005500
MouseAccessories20005500 Grouped by category โ€” but rows retained!

All Electronics = 73000 All Accessories = 5500

๐Ÿ’ป GROUP BY vs Window Function โ€” Same Query, Different Output:

-- WITH GROUP BY (rows collapse!)
SELECT category, SUM(amount) AS category_total
FROM sales
GROUP BY category;

-- WITH WINDOW FUNCTION (rows retained!)
SELECT
product_name,
category,
amount,
SUM(amount) OVER (
PARTITION BY category
) AS category_total

FROM sales;

GROUP BY Output (3 rows): Window Function Output (5 rows):

categorytotal
Accessories5500
Electronics73000
Furniture8000
product_namecategoryamounttotal
ChairFurniture80008000
LaptopElectronics5500073000
MonitorElectronics1800073000
KeyboardAccessories35005500
MouseAccessories20005500
โš ๏ธ Common Mistakes:
โ€ข OVER() clause bhoolna โ€” SUM(amount) aggregate hai, SUM(amount) OVER() window function hai. OVER() zaroori hai window function banane ke liye.
โ€ข PARTITION BY aur GROUP BY confuse karna โ€” GROUP BY rows collapse karta hai, PARTITION BY rows retain karta hai window ke andar.

Topic 3: Sample Dataset โ€” Sales Data

๐Ÿ“˜ Definition: A consistent sample dataset that we'll use throughout ALL window function examples. This is a realistic Sales table with products, categories, regions, salesperson, amount, and date โ€” perfect for demonstrating rankings, running totals, MoM growth, and other window function use cases.

๐ŸŽฏ Explanation: Isi dataset ko hum saare topics mein use karenge โ€” ROW_NUMBER, RANK, LAG, LEAD, Running Total sab. Data mein multiple categories, regions, salespersons, aur dates hain โ€” real-world jaisa. Yeh dataset ek baar create kar lo, phir saari queries directly copy karke chala sakte ho.

๐Ÿ’ป Create the Sales Table:

-- Create Sales table
CREATE TABLE sales ( sale_id INT PRIMARY KEY, product_name VARCHAR(100), category VARCHAR(50), region VARCHAR(50), salesperson VARCHAR(100), amount INT, sale_date DATE );

-- Insert sample data

INSERT INTO sales
(sale_id, product_name, category, region, salesperson, amount, sale_date)

VALUES
(1, 'Laptop', 'Electronics', 'North', 'Ravi', 55000, '2026-01-05'),
(2, 'Monitor', 'Electronics', 'South', 'Sneha', 18000, '2026-01-10'),
(3, 'Keyboard', 'Accessories', 'North', 'Ravi', 3500, '2026-01-15'),
(4, 'Mouse', 'Accessories', 'South', 'Sneha', 2000, '2026-01-20'),
(5, 'Chair', 'Furniture', 'East', 'Arjun', 8000, '2026-02-05'),
(6, 'Desk', 'Furniture', 'East', 'Arjun', 12000, '2026-02-10'),
(7, 'Laptop', 'Electronics', 'West', 'Priya', 60000, '2026-02-15'),
(8, 'Tablet', 'Electronics', 'North', 'Ravi', 25000, '2026-02-20'),
(9, 'Headphones','Accessories', 'West', 'Priya', 4500, '2026-03-05'),
(10, 'Sofa', 'Furniture', 'South', 'Sneha', 35000, '2026-03-10'),
(11, 'Monitor', 'Electronics', 'East', 'Arjun', 22000, '2026-03-15'),
(12, 'Keyboard', 'Accessories', 'West', 'Priya', 3500, '2026-03-20');

๐Ÿ“Š View the Sales Data:

SELECT *
FROM sales
ORDER BY sale_id;
sale_idproduct_namecategoryregionsalespersonamountsale_date
1LaptopElectronicsNorthRavi550002026-01-05
2MonitorElectronicsSouthSneha180002026-01-10
3KeyboardAccessoriesNorthRavi35002026-01-15
4MouseAccessoriesSouthSneha20002026-01-20
5ChairFurnitureEastArjun80002026-02-05
6DeskFurnitureEastArjun120002026-02-10
7LaptopElectronicsWestPriya600002026-02-15
8TabletElectronicsNorthRavi250002026-02-20
9HeadphonesAccessoriesWestPriya45002026-03-05
10SofaFurnitureSouthSneha350002026-03-10
11MonitorElectronicsEastArjun220002026-03-15
12KeyboardAccessoriesWestPriya35002026-03-20
12rows |3categories |4regions |

4 salespersons | Jan-Mar 2026

๐Ÿ“‹ Dataset Summary:
โ€ข Categories: Electronics, Accessories, Furniture
โ€ข Regions: North, South, East, West
โ€ข Salespersons: Ravi, Sneha, Arjun, Priya
โ€ข Date Range: January to March 2026
โ€ข Amount Range: โ‚น2,000 to โ‚น60,000
Perfect for demonstrating rankings, groupings, time-based calculations!

Topic 4: ROW_NUMBER() โ€” Unique Sequential Numbers

๐Ÿ“˜ Definition: ROW_NUMBER() assigns a UNIQUE sequential integer to each row within a partition, starting from 1. Even if two rows have IDENTICAL values, they get DIFFERENT row numbers (based on ORDER BY sequence). It's the most commonly used ranking function โ€” perfect for "top N per group", "remove duplicates", and "select every Nth row" scenarios.

๐ŸŽฏ Explanation: ROW_NUMBER() = serial number dena โ€” 1, 2, 3, 4, 5... Har row ko unique number milta hai, DUPLICATES nahi hote. PARTITION BY se groups banao โ€” har group mein 1 se restart hoga. ORDER BY se decide karo kaunsa row pehla ho (usually amount DESC โ€” highest first). Real use cases: "Har region ka top seller nikalo", "Duplicate rows delete karo (row_num > 1 rakhne wale)", "Pagination โ€” 10 rows per page".

๐Ÿ“‹ Syntax:

ROW_NUMBER() OVER ( [PARTITION BY column_name] ORDER BY column_name [ASC | DESC] )
Rules:
โ€ข
ORDER BY is REQUIRED โ€” without it, row
order is unpredictable
โ€ข PARTITION BY is OPTIONAL โ€” if omitted, entire table is one partition
โ€ข Returns unique 1, 2, 3, 4... within each partition
โ€ข Ties in
ORDER BY are broken arbitrarily (unique numbers regardless)

๐Ÿ’ป Example 1 โ€” Basic Row Numbering:

-- Assign row numbers to all sales ordered by amount (highest first)
SELECT ROW_NUMBER() OVER ( ORDER BY amount DESC ) AS row_num,
product_name, category, amount
FROM sales;
row_numproduct_namecategoryamount
1LaptopElectronics60000
โ† Highest2Laptop
Electronics550003Sofa
Furniture350004Tablet
Electronics250005Monitor
Electronics220006Monitor
Electronics180007Desk
Furniture120008Chair
Furniture80009Headphones
Accessories450010Keyboard
Accessories3500โ† Tie with row11
11Keyboard
Accessories3500โ† Same amount but different row_num!12
MouseAccessories2000 โ† Lowest

๐Ÿ’ป Example 2 โ€” Row Numbers Per Category (PARTITION BY):

-- Rank products within each category (highest amount first)
SELECT category, product_name, amount, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY amount DESC ) AS rank_in_category
FROM sales
ORDER BY category, rank_in_category;
categoryproduct_nameamountrank_in_category
AccessoriesHeadphones45001 โ† Top in Accessories
AccessoriesKeyboard35002
AccessoriesKeyboard35003 โ† Same amount, different rank Accessories
Mouse20004Electronics
Laptop600001 โ† Top in ElectronicsElectronics
Laptop550002Electronics
Tablet250003Electronics
Monitor220004Electronics
Monitor180005Furniture
Sofa350001 โ† Top in FurnitureFurniture
Desk120002Furniture

Each category restarts from 1! Chair 8000 3

๐Ÿ’ป Example 3 โ€” Top N Per Group (Real Interview Question):

-- Find top 2 products in each category by amount WITH ranked_sales AS (
SELECT category, product_name, amount, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY amount DESC ) AS rn
FROM sales ) SELECT category, product_name, amount, rn AS position
FROM ranked_sales
WHERE rn <= 2
ORDER BY category, rn;
categoryproduct_nameamountposition
AccessoriesHeadphones45001
โ† Top2Accessories AccessoriesKeyboard
35002ElectronicsLaptop
600001โ† Top2
Electronics ElectronicsLaptop550002
FurnitureSofa350001
โ† Top2Furniture FurnitureDesk

Top 2 per category โ€” using ROW_NUMBER + WHERE! 12000 2

โš ๏ธ Common Mistakes:
โ€ข ORDER BY skip karna โ€” ROW_NUMBER() OVER() without ORDER BY unpredictable results deta hai. Hamesha ORDER BY specify karo.
โ€ข Top N filtering WHERE mein directly likhna โ€” WHERE ROW_NUMBER() OVER (...) <= 2 ERROR! Window functions WHERE clause mein directly use nahi hote โ€” pehle CTE ya subquery mein calculate karo, phir outer query mein WHERE lagao.

Topic 5: RANK() vs DENSE_RANK() โ€” Handle Ties

๐Ÿ“˜ Definition: Both RANK() and DENSE_RANK() assign rank based on ORDER BY values. Key difference: when TIES occur (equal values), RANK() leaves GAPS in the sequence (1, 2, 2, 4 โ€” skips 3), while DENSE_RANK() gives CONSECUTIVE ranks (1, 2, 2, 3 โ€” no gaps). ROW_NUMBER() gives unique numbers regardless of ties.

๐ŸŽฏ Explanation: Teen ranking functions ka comparison โ€” ROW_NUMBER (hamesha unique), RANK (ties same rank, gap SKIP), DENSE_RANK (ties same rank, NO gap). Real example: 3 employees ka salary 100K hai โ€” teeno ka rank same hona chahiye. RANK() teeno ko rank 1 dega, next employee ko rank 4 (rank 2, 3 skip). DENSE_RANK() teeno ko rank 1 dega, next employee ko rank 2 (no skip). Interview mein Nth highest salary question โ€” DENSE_RANK sabse safe hai kyunki ties handle karta hai bina gaps ke.

๐Ÿ“‹ Comparison โ€” ROW_NUMBER vs RANK vs DENSE_RANK:

Function Tie Handling Sequence Example
ROW_NUMBER() Always unique โ€” no ties 1, 2, 3, 4, 5
RANK() Same rank for ties, GAPS after 1, 2, 2, 4, 5 (skips 3)
DENSE_RANK() Same rank for ties, NO gaps 1, 2, 2, 3, 4 (no skip)

๐Ÿ’ป Example 1 โ€” Compare All 3 Functions Side by Side:

-- Compare ROW_NUMBER, RANK, DENSE_RANK on same data
SELECT product_name, amount, ROW_NUMBER() OVER (ORDER BY amount DESC) AS row_num,
RANK() OVER (ORDER BY amount DESC) AS rank_num, DENSE_RANK() OVER (ORDER BY amount DESC) AS dense_rank
FROM sales
ORDER BY amount DESC;
Notice the difference at Mouse:
โ€ข row_num: 12 (always unique)
โ€ข rank_num: 12 (skipped 11 because tie above)
โ€ข dense_rank: 11 (no gap)

๐Ÿ’ป Example 2 โ€” Nth Highest Salary (Classic Interview Question):

-- Find the 3rd highest sale amount (using DENSE_RANK) WITH ranked_sales AS (
SELECT product_name, amount, DENSE_RANK() OVER ( ORDER BY amount DESC ) AS rnk
FROM sales ) SELECT product_name, amount
FROM ranked_sales
WHERE rnk = 3;
DENSE_RANK is safer than RANK for "Nth highest" queries
โ€” handles ties without skipping ranks!

๐Ÿ’ป Example 3 โ€” Ranking Per Category:

-- Rank salespersons by total sales within each region
SELECT region, salesperson, SUM(amount) AS total_sales, DENSE_RANK() OVER ( PARTITION BY region ORDER BY SUM(amount) DESC ) AS rank_in_region
FROM sales
GROUP BY region, salesperson
ORDER BY region, rank_in_region;
Each region has one top performer!
โš ๏ธ Common Mistakes:
โ€ข RANK() ka use "Nth highest" queries mein โ€” agar ties hain toh gap ki wajah se rank N kabhi exist hi nahi karega. Example: 1, 2, 2, 4 mein "rank = 3" query kuch return nahi karegi! DENSE_RANK safer hai โ€” 1, 2, 2, 3 pattern.
โ€ข Kaunsa use kab karein? Sports rankings (Olympic medals) โ†’ RANK. Nth highest queries โ†’ DENSE_RANK. Serial numbers/pagination โ†’ ROW_NUMBER.

Topic 6: NTILE() โ€” Divide into N Equal Buckets

๐Ÿ“˜ Definition: NTILE(n) divides ordered rows into n approximately equal groups (buckets) and assigns a bucket number (1 to n) to each row. If rows can't be divided equally, earlier buckets get one extra row. Common uses: quartiles (NTILE(4)), deciles (NTILE(10)), percentiles (NTILE(100)) โ€” perfect for customer segmentation, performance categorization, and data distribution analysis.

๐ŸŽฏ Explanation: NTILE(4) matlab data ko 4 equal buckets mein baanto โ€” bucket 1 = top 25%, bucket 2 = 25-50%, etc. NTILE(10) = deciles, NTILE(100) = percentiles. Real business use: "Top 25% customers identify karo" (NTILE(4) where bucket = 1). "Salary quartiles" โ€” top earners kaun hain. "Product performance tiers" โ€” A/B/C category products. HR mein "performance ranking" ke liye bhi use hota hai โ€” top 20% performers.

๐Ÿ“‹ How NTILE Distributes Rows:

NTILE Distribution Examples:
12 rows into NTILE(4): 10 rows into NTILE(3):
Bucket 1: 3 rows Bucket 1: 4 rows (extra 1)
Bucket 2: 3 rows Bucket 2: 3 rows
Bucket 3: 3 rows Bucket 3: 3 rows
Bucket 4: 3 rows (10รท3 = 3.33, so first bucket gets extra)

11 rows into NTILE(4):
Bucket 1: 3 rows (extra 1)
Bucket 2: 3 rows (extra 1)
Bucket 3: 3 rows (extra 1) Wait, 3+3+3+2 = 11 โœ…
Bucket 4: 2 rows

Rule: Earlier buckets get extra rows if not divisible evenly

๐Ÿ’ป Example 1 โ€” Divide Sales into 4 Buckets (Quartiles):

-- Divide sales into 4 quartiles based on amount
SELECT product_name, amount, NTILE(4) OVER ( ORDER BY amount DESC ) AS quartile
FROM sales
ORDER BY amount DESC;
12 rows รท 4 buckets = 3 rows per bucket โœ…

๐Ÿ’ป Example 2 โ€” Top 25% High-Value Sales:

-- Find TOP 25% sales (highest quartile only) WITH quartiles AS (
SELECT product_name, category, region, amount, NTILE(4) OVER ( ORDER BY amount DESC ) AS quartile
FROM sales ) SELECT product_name, category, region, amount
FROM quartiles
WHERE quartile = 1
ORDER BY amount DESC;
Top 25% high-value sales โ€” target for premium marketing!

๐Ÿ’ป Example 3 โ€” Performance Tiers (A/B/C Category):

-- Categorize sales into A (top), B (middle), C (bottom) tiers
SELECT product_name, category, amount, CASE NTILE(3) OVER (ORDER BY amount DESC)
WHEN 1
THEN 'A - High Value'
WHEN 2
THEN 'B - Medium Value'
WHEN 3
THEN 'C - Low Value'
END AS tier
FROM sales
ORDER BY amount DESC;
Product tier categorization โ€” ready for marketing strategy!
โš ๏ธ Common Mistakes:
โ€ข NTILE ke saath ORDER BY zaroori hai โ€” bina order ke random buckets banenge.
โ€ข NTILE ties handle nahi karta smartly โ€” same amount wale rows different buckets mein ja sakte hain (yeh common gotcha hai). Agar ties matter karti hain toh DENSE_RANK better hai NTILE se.
โ€ข Bucket count sensibly choose karo โ€” NTILE(4) 12 rows par sahi, but NTILE(10) sirf 12 rows par uneven distribution dega.

Topic 7: LAG() and LEAD() โ€” Previous/Next Row Access

๐Ÿ“˜ Definition: LAG(column, offset, default) accesses a value from a PREVIOUS row without a self-join. LEAD(column, offset, default) accesses a value from the NEXT row. Both take 3 parameters: the column to fetch, how many rows back/forward (default 1), and a default value if no such row exists (default NULL). These are essential for period-over-period comparisons โ€” MoM growth, YoY comparison, previous vs current.

๐ŸŽฏ Explanation: LAG = "pichli row ki value dikhao" (jaise pichle month ki sales). LEAD = "agli row ki value dikhao" (next month ki sales). MoM growth calculate karne ke liye perfect โ€” (current - LAG(current)) / LAG(current) ร— 100. Bina LAG/LEAD ke self-join karna padta โ€” complex aur slow. LAG/LEAD simple aur fast hai. Data analysts ke liye must-know function โ€” dashboards mein daily use hota hai.

๐Ÿ“‹ Syntax:

LAG(column, offset, default_value) OVER ( [PARTITION BY column_name] ORDER BY column_name )
LEAD(column, offset, default_value) OVER (
[PARTITION BY column_name]
ORDER BY column_name
)

Parameters:
โ€ข column โ†’ which column to fetch
โ€ข
offset โ†’ how many rows back/forward (default 1)
โ€ข default_value โ†’ what to return if no such row exists (default NULL)

๐Ÿ’ป Example 1 โ€” Basic LAG and LEAD:

-- Show current, previous, and next sale for each row
SELECT sale_id, product_name, amount, LAG(amount, 1, 0) OVER ( ORDER BY sale_id ) AS previous_amount,
LEAD(amount, 1, 0) OVER ( ORDER BY sale_id ) AS next_amount
FROM sales
ORDER BY sale_id;
sale_id product_name amount previous_amount next_amount
1 Laptop 55000 0 (default โ€” no prev) 18000
2 Monitor 18000 55000 3500
3 Keyboard 3500 18000 2000
4 Mouse 2000 3500 8000
5 Chair 8000 2000 12000
12 Keyboard 3500 22000 0 (default โ€” no next)

๐Ÿ’ป Example 2 โ€” Month-over-Month Growth (Real Use Case):

-- Monthly sales with MoM growth percentage WITH monthly_sales AS (
SELECT DATE_FORMAT(sale_date, '%Y-%m') AS month, SUM(amount) AS total_sales
FROM sales
GROUP BY DATE_FORMAT(sale_date, '%Y-%m') ) SELECT month, total_sales,
LAG(total_sales, 1) OVER ( ORDER BY month ) AS prev_month_sales,
ROUND( (total_sales - LAG(total_sales) OVER (ORDER BY month)) * 100.0 / LAG(total_sales) OVER (ORDER BY month), 2 ) AS mom_growth_pct
FROM monthly_sales
ORDER BY month;
month total_sales prev_month_sales mom_growth_pct
2026-01 78500 NULL NULL (no prev month)
2026-02 105000 78500 +33.76% ๐Ÿ“ˆ
2026-03 65000 105000 -38.10% ๐Ÿ“‰

๐Ÿ’ป Example 3 โ€” LAG with PARTITION BY (Per Category):

-- Previous sale amount within same category
SELECT category, product_name, sale_date, amount, LAG(amount, 1) OVER ( PARTITION BY category ORDER BY sale_date ) AS prev_sale_in_category
FROM sales
ORDER BY category, sale_date;
category product_name sale_date amount prev_sale_in_category
Accessories Keyboard 2026-01-15 3500 NULL (first in category)
Accessories Mouse 2026-01-20 2000 3500
Electronics Laptop 2026-01-05 55000 NULL (first in category)
Electronics Monitor 2026-01-10 18000 55000
Furniture Chair 2026-02-05 8000 NULL (first in category)
Furniture Desk 2026-02-10 12000 8000
โš ๏ธ Common Mistakes:
โ€ข Default value nahi dena โ€” first row ka LAG NULL aayega, calculations mein error. Hamesha default do: LAG(amount, 1, 0).
โ€ข ORDER BY galat dena โ€” LAG "previous" ka matlab depend karta hai order pe. Time-based analysis ke liye ORDER BY date use karo, na ki ORDER BY amount.
โ€ข DIVIDE by zero avoid karo MoM growth mein โ€” agar previous month sales 0 hain toh division error. NULLIF ya CASE WHEN use karo.

Topic 8: FIRST_VALUE() and LAST_VALUE()

๐Ÿ“˜ Definition: FIRST_VALUE(column) returns the FIRST value in an ordered window/partition. LAST_VALUE(column) returns the LAST value in an ordered window/partition. Both are useful for comparing individual rows against the extreme values (first/last) of their group. IMPORTANT: LAST_VALUE requires an explicit frame clause โ€” without it, you get the wrong result (returns current row instead of actual last).

๐ŸŽฏ Explanation: FIRST_VALUE = "har row ke saath us group ki pehli value bhi dikhao." LAST_VALUE = "aakhri value dikhao." Real use: "Har category mein highest priced product ka naam dikhao har row ke saath." "Employee ka salary + department ka top earner ka salary โ€” compare karo." LAST_VALUE ke saath ek gotcha hai โ€” default frame current row tak hai, isliye actual last chahiye toh full frame specify karna padta hai: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.

๐Ÿ’ป Example 1 โ€” Highest Amount Product per Category:

-- Show each product with the TOP selling product name in its category
SELECT category, product_name, amount, FIRST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY amount DESC ) AS top_product,
FIRST_VALUE(amount) OVER ( PARTITION BY category ORDER BY amount DESC ) AS top_amount
FROM sales
ORDER BY category, amount DESC;
category product_name amount top_product top_amount
Accessories Headphones 4500 Headphones 4500
Accessories Keyboard 3500 Headphones 4500
Accessories Mouse 2000 Headphones 4500
Electronics Laptop 60000 Laptop 60000
Electronics Laptop 55000 Laptop 60000
Furniture Sofa 35000 Sofa 35000
Furniture Desk 12000 Sofa 35000

๐Ÿ’ป Example 2 โ€” LAST_VALUE Gotcha (Wrong vs Right):

-- WRONG WAY (returns current row, not actual last!)
SELECT category, product_name, amount, LAST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY amount DESC ) AS wrong_last
FROM sales;

-- CORRECT WAY (with explicit frame)
SELECT
category,
product_name,
amount,
LAST_VALUE(product_name) OVER (
PARTITION BY category
ORDER BY amount DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS correct_lowest

FROM sales

ORDER BY category, amount DESC;
category product_name amount correct_lowest
Accessories Headphones 4500 Mouse
Accessories Keyboard 3500 Mouse
Accessories Mouse 2000 Mouse
Electronics Laptop 60000 Monitor
Electronics Monitor 18000 Monitor

๐Ÿ’ป Example 3 โ€” Compare with Highest/Lowest in Category:

-- Show each product's amount + highest + lowest in its category
SELECT category, product_name, amount, FIRST_VALUE(amount) OVER ( PARTITION BY category ORDER BY amount DESC ) AS highest_in_cat,
LAST_VALUE(amount) OVER ( PARTITION BY category ORDER BY amount DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS lowest_in_cat,
FIRST_VALUE(amount) OVER ( PARTITION BY category ORDER BY amount DESC ) - amount AS gap_from_top
FROM sales
ORDER BY category, amount DESC;
category product_name amount highest_in_cat lowest_in_cat gap_from_top
Electronics Laptop 60000 60000 18000 0
Electronics Laptop 55000 60000 18000 5000
Electronics Monitor 18000 60000 18000 42000
โš ๏ธ CRITICAL โ€” LAST_VALUE Gotcha: LAST_VALUE ke saath default frame current row tak jaata hai โ€” jab tak explicit frame nahi dete ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, wrong results milte hain! Yeh interview mein bahut common trick question hai. Yaad rakho โ€” LAST_VALUE ke saath ALWAYS explicit frame do.

Topic 9: NTH_VALUE() โ€” Specific Position Value

๐Ÿ“˜ Definition: NTH_VALUE(column, N) returns the value of the specified column from the Nth row within a window/partition. Unlike FIRST_VALUE (1st row) and LAST_VALUE (last row), NTH_VALUE gives you flexibility to fetch ANY specific position. It's less commonly used but powerful for scenarios like "show me the 2nd or 3rd highest value in each group."

๐ŸŽฏ Explanation: NTH_VALUE = specific position ka value nikaalo. FIRST_VALUE = 1st, LAST_VALUE = last, NTH_VALUE(col, 2) = 2nd, NTH_VALUE(col, 3) = 3rd, etc. Real use: "Har category ka 2nd highest product dikhao" โ€” perfect for medal-style rankings. Same LAST_VALUE gotcha โ€” explicit frame ki zaroorat padti hai proper output ke liye.

๐Ÿ’ป Example โ€” Show 2nd Highest in Each Category:

-- Show 1st, 2nd, and 3rd highest products in each category
SELECT DISTINCT category, FIRST_VALUE(product_name) OVER ( PARTITION BY category ORDER BY amount DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS top_1st,
NTH_VALUE(product_name, 2) OVER ( PARTITION BY category ORDER BY amount DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS top_2nd,
NTH_VALUE(product_name, 3) OVER ( PARTITION BY category ORDER BY amount DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS top_3rd
FROM sales;
category top_1st top_2nd top_3rd
Accessories Headphones Keyboard Keyboard
Electronics Laptop Laptop Tablet
Furniture Sofa Desk Chair
โš ๏ธ Common Mistakes:
โ€ข NTH_VALUE ke saath bhi LAST_VALUE jaisa frame gotcha hai โ€” bina explicit frame ke wrong results.
โ€ข Agar partition mein kam rows hain (jaise sirf 2 rows aur tumne NTH_VALUE(col, 3) maanga) toh NULL return hoga โ€” kuch categories mein 3rd position exist nahi karti.

Topic 10: Aggregate Window Functions โ€” Running Totals & Moving Averages

๐Ÿ“˜ Definition: Regular aggregate functions (SUM, AVG, COUNT, MIN, MAX) can be used AS WINDOW FUNCTIONS by adding the OVER() clause. This transforms them from collapsing rows (like GROUP BY) into keeping all rows while adding cumulative calculations. This enables Running Totals, Moving Averages, Cumulative Counts, and other time-series analytics โ€” extremely powerful for dashboards and trend analysis.

๐ŸŽฏ Explanation: Jab SUM/AVG/COUNT ke saath OVER() lagate hain โ€” yeh cumulative kaam karte hain instead of collapsing. Running Total = "abhi tak ka total" โ€” har row par pichli sab rows ka sum. Moving Average = "last N rows ka average" โ€” trends smooth karta hai. Business dashboards mein YTD sales, cumulative customer count, 7-day moving average โ€” sab yehi se banti hain. Data analysts ke liye MUST-KNOW.

๐Ÿ’ป Example 1 โ€” Running Total (Cumulative Sum):

-- Running total of sales by date
SELECT sale_id, sale_date, product_name, amount, SUM(amount) OVER ( ORDER BY sale_date, sale_id ) AS running_total
FROM sales
ORDER BY sale_date, sale_id;
sale_id sale_date product_name amount running_total
1 2026-01-05 Laptop 55000 55000
2 2026-01-10 Monitor 18000 73000
3 2026-01-15 Keyboard 3500 76500
4 2026-01-20 Mouse 2000 78500
5 2026-02-05 Chair 8000 86500
12 2026-03-20 Keyboard 3500 248500

๐Ÿ’ป Example 2 โ€” Running Total Per Category:

-- Running total within each category
SELECT category, sale_date, product_name, amount, SUM(amount) OVER ( PARTITION BY category ORDER BY sale_date ) AS cat_running_total,
COUNT(*) OVER ( PARTITION BY category ORDER BY sale_date ) AS cat_running_count
FROM sales
ORDER BY category, sale_date;
category sale_date product_name amount cat_running_total cat_running_count
Accessories 2026-01-15 Keyboard 3500 3500 1
Accessories 2026-01-20 Mouse 2000 5500 2
Accessories 2026-03-05 Headphones 4500 10000 3
Accessories 2026-03-20 Keyboard 3500 13500 4
Electronics 2026-01-05 Laptop 55000 55000 1
Electronics 2026-01-10 Monitor 18000 73000 2

๐Ÿ’ป Example 3 โ€” Category Total Comparison + % of Total:

-- Show each sale amount + category total + % of category total
SELECT category, product_name, amount, SUM(amount) OVER ( PARTITION BY category ) AS category_total,
ROUND( amount * 100.0 / SUM(amount) OVER (PARTITION BY category), 2 ) AS pct_of_category,
ROUND( AVG(amount) OVER (PARTITION BY category), 0 ) AS category_avg
FROM sales
ORDER BY category, amount DESC;
category product_name amount category_total pct_of_category category_avg
Accessories Headphones 4500 13500 33.33% 3375
Accessories Keyboard 3500 13500 25.93% 3375
Electronics Laptop 60000 180000 33.33% 36000
Electronics Laptop 55000 180000 30.56% 36000
โš ๏ธ Common Mistakes:
โ€ข Running total ke liye ORDER BY zaroori hai โ€” bina ORDER BY ke SUM() OVER() sirf total sum dega har row mein (like OVER() empty).
โ€ข PARTITION BY use karna bhoolna jab per-group running total chahiye โ€” poore data ka running total ban jaayega.

Topic 11: Window Frames โ€” ROWS/RANGE BETWEEN

๐Ÿ“˜ Definition: Window Frames define EXACTLY which rows are included in the window calculation for each row. By default, when you use ORDER BY without a frame, the frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. You can customize this with ROWS BETWEEN N PRECEDING AND M FOLLOWING to create Moving Averages (3-day, 7-day) or centered calculations. This is the most advanced window function concept.

๐ŸŽฏ Explanation: Frame = "window ke andar kaunse rows include karein calculation ke liye." Default: beginning se current row tak. Custom frames se moving average ban sakta hai โ€” "current row + last 2 rows ka average" (3-day moving avg). Bahut powerful for trend analysis โ€” daily fluctuations smooth karke long-term trends dikhata hai. Stock market, sales trends, KPI monitoring โ€” sab mein moving averages use hote hain.

๐Ÿ“‹ Frame Clause Syntax:

Frame Options:
ROWS BETWEEN  AND

Common Frame Patterns:

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
โ†’
From beginning to current row (default with ORDER BY)
โ†’ Used for: Running Total

ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
โ†’ Current row + previous 2 rows (3 total)
โ†’ Used for: 3-row Moving Average

ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
โ†’ Previous + current + next (centered 3 rows)
โ†’ Used for: Centered smoothing

ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
โ†’
From current to
end
โ†’ Used for: "How much left after this row?"

ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
โ†’ Entire partition
โ†’ Used for: Overall totals (fixed for LAST_VALUE)

๐Ÿ’ป Example 1 โ€” 3-Row Moving Average:

-- 3-row moving average of sales amount
SELECT sale_id, sale_date, amount, ROUND( AVG(amount) OVER ( ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ), 2 ) AS moving_avg_3row
FROM sales
ORDER BY sale_date;
sale_id sale_date amount moving_avg_3row
1 2026-01-05 55000 55000.00 (only 1 row)
2 2026-01-10 18000 36500.00 (avg of 2 rows)
3 2026-01-15 3500 25500.00 (avg of 3 rows)
4 2026-01-20 2000 7833.33 (rolling 3)
5 2026-02-05 8000 4500.00 (rolling 3)
6 2026-02-10 12000 7333.33 (rolling 3)

๐Ÿ’ป Example 2 โ€” Comparing Different Frame Types:

-- Compare different frame types side by side
SELECT sale_id, sale_date, amount, SUM(amount) OVER ( ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS running_total,
SUM(amount) OVER ( ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) AS sum_prev_curr_next,
SUM(amount) OVER ( ORDER BY sale_date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING ) AS remaining_from_here
FROM sales
ORDER BY sale_date;
sale_id amount running_total sum_prev_curr_next remaining_from_here
1 55000 55000 73000 248500
2 18000 73000 76500 193500
3 3500 76500 23500 175500
4 2000 78500 13500 172000
โš ๏ธ Common Mistakes:
โ€ข ROWS vs RANGE confuse karna โ€” ROWS physical row count par based hai, RANGE value-based hai (same values wale rows include hote hain). Beginner ke liye ROWS use karo โ€” more predictable.
โ€ข Moving average ke pehle rows mein incomplete data โ€” first 2 rows mein sirf 1 ya 2 row ka average aayega (3 nahi). Yeh expected behavior hai โ€” visualization mein handle karo.

Topic 12: Real Scenario Problems โ€” Interview Classics

๐Ÿ“˜ Definition: Real-world scenarios that combine multiple window functions concepts. These are the exact problems asked in data analyst interviews and needed in daily dashboard work. Master these 5 scenarios and you'll be able to solve 90% of window function problems in interviews and real projects.

๐Ÿ’ป Scenario 1 โ€” Top N Per Group (Top 2 sales per region):

-- Top 2 highest sales in each region WITH ranked_sales AS (
SELECT region, product_name, salesperson, amount, ROW_NUMBER() OVER ( PARTITION BY region ORDER BY amount DESC ) AS rn
FROM sales ) SELECT region, product_name, salesperson, amount
FROM ranked_sales
WHERE rn <= 2
ORDER BY region, amount DESC;
region product_name salesperson amount
East Monitor Arjun 22000
East Desk Arjun 12000
North Laptop Ravi 55000
North Tablet Ravi 25000
South Sofa Sneha 35000
South Monitor Sneha 18000
West Laptop Priya 60000
West Headphones Priya 4500

๐Ÿ’ป Scenario 2 โ€” Salesperson Total + Rank + % of Company Total:

-- Complete salesperson performance analysis
SELECT salesperson, SUM(amount) AS total_sales, COUNT(*) AS total_orders,
RANK() OVER ( ORDER BY SUM(amount) DESC ) AS sales_rank,
ROUND( SUM(amount) * 100.0 / SUM(SUM(amount)) OVER (), 2 ) AS pct_of_company
FROM sales
GROUP BY salesperson
ORDER BY total_sales DESC;
salesperson total_sales total_orders sales_rank pct_of_company
Ravi 83500 3 1 33.60%
Priya 68000 3 2 27.36%
Sneha 55000 3 3 22.13%
Arjun 42000 3 4 16.90%

๐Ÿ’ป Scenario 3 โ€” Compare Current Sale with Previous (LAG):

-- Compare each sale with previous sale (day-over-day)
SELECT sale_id, sale_date, product_name, amount, LAG(amount, 1, 0) OVER ( ORDER BY sale_date, sale_id ) AS previous_amount,
amount - LAG(amount, 1, 0) OVER ( ORDER BY sale_date, sale_id ) AS change_amount,
CASE
WHEN amount > LAG(amount) OVER (ORDER BY sale_date, sale_id)
THEN '๐Ÿ“ˆ Increased'
WHEN amount < LAG(amount) OVER (ORDER BY sale_date, sale_id)
THEN '๐Ÿ“‰ Decreased'
ELSE 'First Sale'
END AS trend
FROM sales
ORDER BY sale_date, sale_id;
sale_id product_name amount previous_amount change_amount trend
1 Laptop 55000 0 55000 First Sale
2 Monitor 18000 55000 -37000 ๐Ÿ“‰ Decreased
5 Chair 8000 2000 6000 ๐Ÿ“ˆ Increased
7 Laptop 60000 12000 48000 ๐Ÿ“ˆ Increased

๐Ÿ’ป Scenario 4 โ€” Nth Highest Sale Per Category:

-- Find 2nd highest sale in each category (DENSE_RANK handles ties) WITH ranked_sales AS (
SELECT category, product_name, amount, DENSE_RANK() OVER ( PARTITION BY category ORDER BY amount DESC ) AS rnk
FROM sales ) SELECT category, product_name, amount
FROM ranked_sales
WHERE rnk = 2
ORDER BY category;
category product_name amount
Accessories Keyboard 3500
Accessories Keyboard 3500
Electronics Laptop 55000
Furniture Desk 12000

๐Ÿ’ป Scenario 5 โ€” Cumulative % Contribution (Pareto Analysis):

-- 80/20 rule โ€” cumulative % of sales by product
SELECT product_name, amount, SUM(amount) OVER ( ORDER BY amount DESC ) AS running_total,
ROUND( SUM(amount) OVER (ORDER BY amount DESC) * 100.0 / SUM(amount) OVER (), 2 ) AS cumulative_pct
FROM sales
ORDER BY amount DESC;
product_name amount running_total cumulative_pct
Laptop 60000 60000 24.14%
Laptop 55000 115000 46.28%
Sofa 35000 150000 60.36%
Tablet 25000 175000 70.42%
Monitor 22000 197000 79.28%
Monitor 18000 215000 86.52%
๐Ÿ“‹ Pareto Analysis Insight: Top 5 products (Laptop x2, Sofa, Tablet, Monitor) contribute ~80% of total sales. Yeh 80/20 rule (Pareto Principle) hai โ€” 20% products se 80% revenue. Business decisions ke liye bahut valuable insight!

Window Functions โ€” Complete Quick Reference

Function Purpose Use Case
ROW_NUMBER() Unique sequential numbers Top N per group, duplicates removal
RANK() Ties same rank, gaps after Sports rankings, competition
DENSE_RANK() Ties same rank, no gaps Nth highest queries
NTILE(n) Divide into N buckets Quartiles, deciles, tiers
LAG(col) Previous row value MoM growth, day-over-day comparison
LEAD(col) Next row value Forecast comparison, next event
FIRST_VALUE(col) First value in window Compare with group's best/first
LAST_VALUE(col) Last value in window (needs frame!) Compare with group's worst/last
NTH_VALUE(col, N) Nth position value (needs frame!) 2nd or 3rd highest values
SUM() OVER() Running total or group total YTD sales, cumulative revenue
AVG() OVER() Moving average 7-day/30-day moving avg
ROWS BETWEEN Custom frame definition 3-day moving avg, custom windows

๐ŸŽ‰ SQL Window Functions โ€” Complete!

12 topics mein SQL Window Functions ka complete deep dive โ€” Introduction se lekar Real Scenarios tak. ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE, Aggregate Windows, Frames, aur 5 real-world scenarios. Yeh SQL ka most powerful feature hai โ€” data analysts ke liye MUST-KNOW. Interviews mein 100% puchha jaata hai. Data Insights SQL Topic Wise ka doosra topic complete! Aage aur SQL topics aayenge โ€” JOINs Masterclass, Subqueries & CTEs, Date Functions, aur bahut kuch. Data Insights par sab FREE, detailed, Hinglish mein.

Happy Querying & Keep Analyzing! ๐Ÿš€

๐Ÿ‘ค
Jatin Kumar
Data Analyst & Educator

Python, SQL, Power BI aur Excel mein practical tutorials likhta hoon โ€” taaki data analytics seekhna aasan ho. Portfolio: jatinanalytics.co.in

Portfolio LinkedIn GitHub Kaggle All Articles
Share:

๐Ÿ’ฌ Comments (0)

Spam/links allowed nahi hain โ€” respectful comments welcome!

Loading comments...

Was this article helpful?
Previous ArticleSQL Data Cleaning Techniques โ€” 2026 Guide (MySQL)Next Article SQL Short Notes

๐Ÿ“š More Articles Like This

"Database Design โ€” ER Diagram, Normalization And Real-World Schema"

Read Article

Advanced MySQL โ€” Views, Indexes, Stored Procedures, Functions, Triggers And Window Functions

Read Article

"Subqueries โ€” Queries ke Andar Queries"

Read Article