SQL Window Functions
SQL

SQL Window Functions: ROW_NUMBER, RANK, LAG and Running Totals

SQL Tutorial · Chapter 30 of 32

SQL window functions perform a calculation across a set of rows related to the current row, without collapsing those rows the way GROUP BY does. Each row keeps its own detail and gains an extra column: a rank, a running total, the previous row’s value or a group total. The OVER clause defines the “window” of rows the function looks at.

SQL Window Function Syntax

function_name(arguments) OVER (
    [PARTITION BY column]   -- restart the calculation for each group
    [ORDER BY column]       -- order rows inside the window
)
Function Returns
ROW_NUMBER() 1, 2, 3 … with no ties
RANK() Rank with gaps after ties (1, 1, 3)
DENSE_RANK() Rank without gaps (1, 1, 2)
SUM / AVG / COUNT / MIN / MAX … OVER Aggregate over the window, kept on every row
LAG(col) / LEAD(col) Value from the previous / next row
FIRST_VALUE / LAST_VALUE / NTILE Edge values and bucket numbers

Sample Table

Orders
OrderID CustomerID Product Amount OrderDate
101 1 Laptop 1200 2026-01-05
102 2 Mouse 25 2026-01-07
103 1 Monitor 300 2026-01-12
104 3 Keyboard 45 2026-02-02
105 6 Headset 80 2026-02-15
106 4 Laptop 1150 2026-03-01

Example 1: ROW_NUMBER

SELECT OrderID, Amount,
       ROW_NUMBER() OVER (ORDER BY Amount DESC) AS RowNum
FROM Orders;
OrderID Amount RowNum
101 1200 1
106 1150 2
103 300 3
105 80 4
104 45 5
102 25 6

All six rows survive. Compare with GROUP BY, which would have returned one row per group.

Example 2: RANK vs DENSE_RANK with Ties

Ranking orders by month creates ties: three orders share January.

SELECT OrderID, SUBSTR(OrderDate, 1, 7) AS Month,
       RANK()       OVER (ORDER BY SUBSTR(OrderDate, 1, 7)) AS Rnk,
       DENSE_RANK() OVER (ORDER BY SUBSTR(OrderDate, 1, 7)) AS DenseRnk
FROM Orders;
OrderID Month Rnk DenseRnk
101 2026-01 1 1
102 2026-01 1 1
103 2026-01 1 1
104 2026-02 4 2
105 2026-02 4 2
106 2026-03 6 3

RANK skips to 4 after three tied rows; DENSE_RANK continues with 2.

Example 3: Running Total with SUM OVER

SELECT OrderID, OrderDate, Amount,
       SUM(Amount) OVER (ORDER BY OrderDate) AS RunningTotal
FROM Orders;
OrderID OrderDate Amount RunningTotal
101 2026-01-05 1200 1200
102 2026-01-07 25 1225
103 2026-01-12 300 1525
104 2026-02-02 45 1570
105 2026-02-15 80 1650
106 2026-03-01 1150 2800

With ORDER BY inside OVER, the default frame is “from the first row up to the current row”, which is exactly a running total.

Example 4: PARTITION BY for Group Totals on Every Row

SELECT OrderID, CustomerID, Amount,
       SUM(Amount) OVER (PARTITION BY CustomerID) AS CustomerTotal,
       ROUND(100.0 * Amount / SUM(Amount) OVER (PARTITION BY CustomerID), 1) AS PctOfCustomer
FROM Orders
ORDER BY CustomerID, OrderID;
OrderID CustomerID Amount CustomerTotal PctOfCustomer
101 1 1200 1500 80.0
103 1 300 1500 20.0
102 2 25 25 100.0
104 3 45 45 100.0
106 4 1150 1150 100.0
105 6 80 80 100.0

Example 5: LAG to Compare with the Previous Row

SELECT OrderID, OrderDate, Amount,
       LAG(Amount) OVER (ORDER BY OrderDate) AS PrevAmount,
       Amount - LAG(Amount) OVER (ORDER BY OrderDate) AS Change
FROM Orders;
OrderID OrderDate Amount PrevAmount Change
101 2026-01-05 1200 NULL NULL
102 2026-01-07 25 1200 -1175
103 2026-01-12 300 25 275
104 2026-02-02 45 300 -255
105 2026-02-15 80 45 35
106 2026-03-01 1150 80 1070

LEAD works the same way but looks at the next row. The first row has no predecessor, so LAG returns NULL.

Example 6: Top-N per Group

Window functions cannot appear in WHERE, so wrap the query in a subquery or CTE and filter on the result. This returns each customer’s largest order.

WITH ranked AS (
  SELECT OrderID, CustomerID, Amount,
         ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY Amount DESC) AS rn
  FROM Orders
)
SELECT OrderID, CustomerID, Amount
FROM ranked
WHERE rn = 1
ORDER BY CustomerID;
OrderID CustomerID Amount
101 1 1200
102 2 25
104 3 45
106 4 1150
105 6 80

Works in MySQL, SQL Server, PostgreSQL, SQLite

  • Window functions are supported in MySQL 8.0+, SQL Server 2012+, PostgreSQL 8.4+ and SQLite 3.25+. MySQL 5.7 and older do not have them.
  • Month extraction differs: SQL Server uses FORMAT(OrderDate, 'yyyy-MM'), MySQL DATE_FORMAT, PostgreSQL TO_CHAR; the window syntax itself is identical.
  • All four support the WINDOW clause to name a window once and reuse it, except SQL Server.

Common Mistakes

  • Putting a window function in WHERE or HAVING; filter through a subquery or CTE instead.
  • Forgetting ORDER BY inside OVER for ROW_NUMBER, RANK, LAG or running totals, which makes the result arbitrary.
  • Confusing RANK and DENSE_RANK when gaps matter for reporting.
  • Expecting SUM() OVER (ORDER BY ...) to give a grand total; without ORDER BY it does, with ORDER BY it is cumulative.

Practice Exercise

  1. Number each customer’s orders from oldest to newest.
  2. Show each order with the average Amount of all orders on the same row.
Show answers
-- 1
SELECT CustomerID, OrderID, OrderDate,
       ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS OrderSeq
FROM Orders;
-- customer 1: 101 = 1, 103 = 2; all others = 1

-- 2
SELECT OrderID, Amount, AVG(Amount) OVER () AS OverallAvg FROM Orders;
-- OverallAvg = 466.67 on every row

Related Chapters

FAQ

What is the difference between a window function and GROUP BY?

GROUP BY merges rows into one row per group. A window function keeps every row and adds the aggregate, rank or offset value as a new column.

What does PARTITION BY do?

It splits the rows into groups so the window function restarts for each group, like GROUP BY but without collapsing the rows.

Can I use a window function in a WHERE clause?

No. Window functions are evaluated after WHERE. Put the query in a subquery or CTE and filter on the window column in the outer query.

Chapter 30 of 32 · SQL from Scratch: all 32 chapters

PK
Meet PK, the founder of NeotechNavigators.com! With over 15 years of experience in Data Visualization, Excel Automation, and dashboard creation. PK is a Microsoft Certified Professional who has a passion for all things in Excel. PK loves to explore new and innovative ways to use Excel and is always eager to share his knowledge with others. With an eye for detail and a commitment to excellence, PK has become a go-to expert in the world of Excel. Whether you're looking to create stunning visualizations or streamline your workflow with automation, PK has the skills and expertise to help you succeed. Join the many satisfied clients who have benefited from PK's services and see how he can take your data analysis skills to the next level!
https://neotechnavigators.com