Window functions for advanced analytics

Maria Garcia Feb 2026
2 tabs
-- ROW_NUMBER: Unique sequential number
SELECT
  name,
  department,
  salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC) as overall_rank,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank
FROM employees;

-- RANK: Gaps after ties
SELECT
  name,
  score,
  RANK() OVER (ORDER BY score DESC) as rank,
  DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank
FROM test_scores;
-- If two people tie for #1, next is #3 with RANK, #2 with DENSE_RANK

-- Running total
SELECT
  order_date,
  amount,
  SUM(amount) OVER (ORDER BY order_date) as running_total
FROM orders;

-- Moving average (last 7 days)
SELECT
  date,
  revenue,
  AVG(revenue) OVER (
    ORDER BY date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) as moving_avg_7day
FROM daily_revenue;

-- LAG/LEAD: Access previous/next rows
SELECT
  date,
  price,
  LAG(price) OVER (ORDER BY date) as prev_price,
  price - LAG(price) OVER (ORDER BY date) as price_change,
  LEAD(price) OVER (ORDER BY date) as next_price
FROM stock_prices;

-- Percentage of total
SELECT
  product,
  sales,
  sales * 100.0 / SUM(sales) OVER () as pct_of_total,
  sales * 100.0 / SUM(sales) OVER (PARTITION BY category) as pct_of_category
FROM product_sales;
2 files · sql Explain with highlit

Window functions perform calculations across row sets without grouping. ROWNUMBER assigns unique sequential numbers. RANK/DENSERANK handle ties differently. I use PARTITION BY to reset calculations per group. ORDER BY determines calculation order within partitions. LAG/LEAD access previous/next rows—useful for deltas and trends. FIRSTVALUE/LASTVALUE grab boundary values. Running totals use cumulative SUM. Moving averages calculate trends. NTILE divides data into buckets. Window functions avoid self-joins and subqueries. RANGE vs ROWS defines window frames differently. Understanding window functions unlocks complex analytics in single queries. They're essential for reporting and data analysis.