Skill Featured

Build Advanced SQL Window Functions

Skill for building SQL window functions: ranking, running totals, moving averages, and cross-platform patterns.

Works with postgresqlsql serveroraclemysqlbigquery

91
Spark score
out of 100
Status Verified Official
Updated 6 months ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Leverage expert SQL window function capabilities to perform complex analytical queries, ranking, aggregation, and offset operations across diverse modern databases.

Outcomes

What it gets done

01

Construct efficient window functions for ranking and sequential analysis.

02

Implement running calculations and moving averages with precise frame specifications.

03

Optimize window function performance through indexing and advanced techniques.

04

Ensure cross-platform compatibility and leverage database-specific features.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-window-function-builder | bash

Overview

Window Function Builder

A skill for writing SQL window functions covering ranking, running totals, moving averages, and cross-platform syntax like BigQuery/Snowflake QUALIFY and SQL Server WITHIN GROUP. Use for analytical SQL needing ranking, running totals, or period comparisons. Assumes standard window function support - pre-8.0 MySQL needs simplified patterns.

What it does

Window Function Builder is a skill for creating optimized SQL window functions for analytics, ranking, running calculations, and business intelligence queries across PostgreSQL, SQL Server, Oracle, MySQL, BigQuery, and Snowflake. It's built on the essential window syntax - FUNCTION() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE BETWEEN ...) - and covers four function categories: ranking (ROW_NUMBER, RANK, DENSE_RANK, NTILE), aggregate (SUM/AVG/COUNT/MIN/MAX with frames), offset (LAG, LEAD, FIRST_VALUE, LAST_VALUE), and statistical (PERCENT_RANK, CUME_DIST, PERCENTILE_CONT).

For ranking and sequential analysis, it shows multi-level ranking with tie handling (ROW_NUMBER vs RANK vs DENSE_RANK vs NTILE quartiles in one query) and a top-N-per-group pattern combining ROW_NUMBER with PERCENT_RANK to select both an absolute top-5 and a percentile-based cutoff.

SELECT 
    date,
    daily_sales,
    SUM(daily_sales) OVER (
        ORDER BY date 
        ROWS UNBOUNDED PRECEDING
    ) as running_total,
    AVG(daily_sales) OVER (
        ORDER BY date 
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) as seven_day_avg
FROM daily_sales
ORDER BY date;

For running calculations, it covers running totals and moving averages with ROWS BETWEEN frame clauses, and year-over-year comparison using LAG with a 12-period offset plus NULLIF-guarded percent-change calculation. Advanced frame specifications cover RANGE-based time windows (e.g. a rolling 30-day average using RANGE BETWEEN INTERVAL) versus ROWS-based windows, and FIRST_VALUE/LAST_VALUE across an unbounded frame to pull partition boundary values.

For complex business calculations, it demonstrates a customer lifecycle analysis combining ROW_NUMBER (order sequence), LAG (previous order date), and a running SUM (lifetime value) to classify each order as First Order, Reactivated (gap over 365 days), or Repeat.

For performance, it recommends composite indexes on PARTITION BY and ORDER BY columns, covering indexes for frequently accessed columns, and monitoring execution plans for sort operations - plus a CTE-based pattern that computes window functions once in a base CTE and reuses the results in the outer query rather than recomputing them.

Cross-platform considerations map feature support and syntax differences: PostgreSQL has full standard compliance and all frame types; SQL Server supports OFFSET/FETCH with window functions and WITHIN GROUP for ordered aggregation; BigQuery and Snowflake support the QUALIFY clause for filtering directly on window function results without a wrapping CTE; MySQL 8.0+ has basic window functions with limited frame support. It shows the BigQuery/Snowflake QUALIFY shortcut for a common top-1-per-group filter and SQL Server's WITHIN GROUP for ordered string aggregation.

Best practices include using appropriate partitioning to avoid oversized window operations, always specifying explicit frame clauses with aggregate functions, handling NULLs explicitly with COALESCE/ISNULL, testing edge cases like empty partitions and single-row groups, using CTEs to keep complex window logic readable, profiling and adjusting partitioning strategy based on real performance, and leveraging platform-specific optimizations like QUALIFY where available.

When to use - and when NOT to

Use this skill when writing analytical SQL that needs ranking, running totals, moving averages, period-over-period comparisons, or top-N-per-group logic - reporting queries, BI dashboards, or customer/product lifecycle analysis built directly in SQL.

It assumes the target database supports standard window function syntax; MySQL versions before 8.0 and databases with limited frame support will need simplified patterns rather than the full RANGE-based or advanced frame specifications shown here.

Inputs and outputs

Inputs: the target database platform and the analytical question to answer (ranking, running total, period comparison, cohort/lifecycle segmentation).

Outputs: optimized SQL queries using appropriate window functions, frame clauses, and platform-specific syntax (QUALIFY, WITHIN GROUP) tuned to the target database.

Integrations

Targets PostgreSQL, SQL Server, Oracle, MySQL 8.0+, BigQuery, and Snowflake, using each platform's native window function syntax and extensions.

Who it's for

Data analysts, BI developers, and backend engineers writing analytical SQL who need correct, performant window function patterns across different database platforms.

Source README

Window Function Builder Expert

You are an expert in SQL window functions with deep knowledge of analytical query patterns, performance optimization, and cross-platform compatibility. You excel at creating efficient window functions for ranking, aggregation, offset operations, and complex business calculations across PostgreSQL, SQL Server, Oracle, MySQL, BigQuery, Snowflake, and other modern databases.

Core Window Function Components

Essential Syntax Structure

  • FUNCTION() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE BETWEEN ...)
  • Partition clause defines calculation groups
  • Order clause determines row sequence for calculations
  • Frame clause specifies which rows to include in calculations
  • Always consider NULL handling and edge cases

Function Categories

  • Ranking: ROW_NUMBER(), RANK(), DENSE_RANK(), NTILE()
  • Aggregate: SUM(), AVG(), COUNT(), MIN(), MAX() with frames
  • Offset: LAG(), LEAD(), FIRST_VALUE(), LAST_VALUE()
  • Statistical: PERCENT_RANK(), CUME_DIST(), PERCENTILE_CONT()

Ranking and Sequential Analysis

-- Multi-level ranking with ties handling
SELECT 
    product_id,
    category,
    sales_amount,
    ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales_amount DESC) as row_num,
    RANK() OVER (PARTITION BY category ORDER BY sales_amount DESC) as rank_with_gaps,
    DENSE_RANK() OVER (PARTITION BY category ORDER BY sales_amount DESC) as dense_rank,
    NTILE(4) OVER (PARTITION BY category ORDER BY sales_amount DESC) as quartile
FROM sales_data;

-- Top N per group with percentage
WITH ranked_sales AS (
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY region ORDER BY revenue DESC) as rn,
        PERCENT_RANK() OVER (PARTITION BY region ORDER BY revenue DESC) as pct_rank
    FROM sales_summary
)
SELECT * FROM ranked_sales 
WHERE rn <= 5 AND pct_rank <= 0.1;

Running Calculations and Moving Averages

-- Running totals and moving averages
SELECT 
    date,
    daily_sales,
    SUM(daily_sales) OVER (
        ORDER BY date 
        ROWS UNBOUNDED PRECEDING
    ) as running_total,
    AVG(daily_sales) OVER (
        ORDER BY date 
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) as seven_day_avg,
    SUM(daily_sales) OVER (
        ORDER BY date 
        ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
    ) as next_three_days
FROM daily_sales
ORDER BY date;

-- Year-over-year comparison with LAG
SELECT 
    year,
    month,
    revenue,
    LAG(revenue, 12) OVER (ORDER BY year, month) as prev_year_revenue,
    revenue - LAG(revenue, 12) OVER (ORDER BY year, month) as yoy_change,
    ROUND(
        (revenue - LAG(revenue, 12) OVER (ORDER BY year, month)) * 100.0 / 
        NULLIF(LAG(revenue, 12) OVER (ORDER BY year, month), 0), 2
    ) as yoy_pct_change
FROM monthly_revenue;

Advanced Frame Specifications

-- Range-based windows for time series
SELECT 
    timestamp,
    value,
    -- Last 30 days using RANGE
    AVG(value) OVER (
        ORDER BY timestamp 
        RANGE BETWEEN INTERVAL '30' DAY PRECEDING 
        AND CURRENT ROW
    ) as avg_30_days,
    -- First and last values in partition
    FIRST_VALUE(value) OVER (
        PARTITION BY category 
        ORDER BY timestamp 
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) as first_value,
    LAST_VALUE(value) OVER (
        PARTITION BY category 
        ORDER BY timestamp 
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) as last_value
FROM time_series_data;

Complex Business Calculations

-- Customer lifecycle analysis
WITH customer_orders AS (
    SELECT 
        customer_id,
        order_date,
        order_amount,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) as order_sequence,
        LAG(order_date) OVER (PARTITION BY customer_id ORDER BY order_date) as prev_order_date,
        SUM(order_amount) OVER (
            PARTITION BY customer_id 
            ORDER BY order_date 
            ROWS UNBOUNDED PRECEDING
        ) as lifetime_value
    FROM orders
)
SELECT 
    customer_id,
    order_date,
    order_amount,
    lifetime_value,
    CASE 
        WHEN order_sequence = 1 THEN 'First Order'
        WHEN DATEDIFF(day, prev_order_date, order_date) > 365 THEN 'Reactivated'
        ELSE 'Repeat'
    END as order_type
FROM customer_orders;

Performance Optimization

Indexing Strategy

  • Create composite indexes on PARTITION BY and ORDER BY columns
  • Consider covering indexes for frequently accessed columns
  • Monitor execution plans for sort operations

Optimization Techniques

-- Efficient window function reuse
WITH base_windows AS (
    SELECT 
        *,
        SUM(amount) OVER (PARTITION BY category ORDER BY date) as running_sum,
        ROW_NUMBER() OVER (PARTITION BY category ORDER BY date DESC) as rn
    FROM transactions
)
SELECT 
    *,
    running_sum,
    CASE WHEN rn = 1 THEN 'Latest' ELSE 'Historical' END as status
FROM base_windows;

Cross-Platform Considerations

Database-Specific Features

  • PostgreSQL: Full standard compliance, supports all frame types
  • SQL Server: OFFSET/FETCH with window functions, WITHIN GROUP
  • BigQuery: QUALIFY clause for filtering window results
  • Snowflake: QUALIFY, window function chaining
  • MySQL 8.0+: Basic window functions, limited frame support

Common Patterns

-- BigQuery QUALIFY syntax
SELECT customer_id, order_date, amount
FROM orders
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1;

-- SQL Server WITHIN GROUP
SELECT 
    department,
    STRING_AGG(employee_name, ', ') WITHIN GROUP (ORDER BY salary DESC) as top_earners
FROM employees
GROUP BY department;

Best Practices

  • Use appropriate partitioning to avoid large window operations
  • Specify explicit frame clauses when using aggregate functions
  • Consider NULL handling with COALESCE or ISNULL in calculations
  • Test window functions with edge cases (empty partitions, single rows)
  • Use CTEs to make complex window operations readable
  • Profile query performance and adjust partitioning strategy accordingly
  • Leverage database-specific optimizations (QUALIFY, specialized functions)
  • Document business logic clearly when using complex frame specifications

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.