Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
SQL

SQL Window Functions: Analytics Queries That Replace Hundreds of Lines of Code

- 05.02.26 - ErcanOPAK

Writing complex analytical queries with self-joins and subqueries? Window functions perform calculations across rows without grouping.

Common Window Function Patterns:

-- Basic structure
SELECT 
    column1,
    column2,
    WINDOW_FUNCTION() OVER (
        PARTITION BY partition_column
        ORDER BY sort_column
        ROWS/RANGE BETWEEN frame_start AND frame_end
    ) AS calculated_column
FROM table_name;

-- Available functions:
-- Ranking: ROW_NUMBER(), RANK(), DENSE_RANK(), NTILE()
-- Analytics: LEAD(), LAG(), FIRST_VALUE(), LAST_VALUE(), NTH_VALUE()
-- Aggregates: SUM(), AVG(), COUNT(), MIN(), MAX() (as window functions)

Real-World E-commerce Analytics:

-- Customer purchasing behavior analysis
SELECT 
    CustomerId,
    OrderDate,
    TotalAmount,
    
    -- Running total per customer
    SUM(TotalAmount) OVER (
        PARTITION BY CustomerId
        ORDER BY OrderDate
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS CustomerLifetimeValue,
    
    -- Average order value per customer
    AVG(TotalAmount) OVER (
        PARTITION BY CustomerId
    ) AS AvgOrderValue,
    
    -- Difference from previous order
    TotalAmount - LAG(TotalAmount, 1) OVER (
        PARTITION BY CustomerId
        ORDER BY OrderDate
    ) AS AmountChangeFromPrevious,
    
    -- Days since last order
    DATEDIFF(DAY, 
        LAG(OrderDate, 1) OVER (
            PARTITION BY CustomerId
            ORDER BY OrderDate
        ),
        OrderDate
    ) AS DaysSinceLastOrder,
    
    -- Rank orders by amount per customer
    RANK() OVER (
        PARTITION BY CustomerId
        ORDER BY TotalAmount DESC
    ) AS OrderRankByAmount,
    
    -- Percent of customer's total spending
    TotalAmount * 100.0 / SUM(TotalAmount) OVER (
        PARTITION BY CustomerId
    ) AS PercentOfCustomerTotal,
    
    -- Moving average of last 3 orders
    AVG(TotalAmount) OVER (
        PARTITION BY CustomerId
        ORDER BY OrderDate
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS MovingAvg3Orders
    
FROM Orders
WHERE OrderDate >= DATEADD(MONTH, -12, GETDATE())
ORDER BY CustomerId, OrderDate;

Advanced: Gap and Island Problems

-- Find continuous date ranges (islands)
WITH DateEvents AS (
    SELECT 
        UserId,
        LoginDate,
        -- Mark start of new island when gap > 1 day
        CASE WHEN DATEDIFF(DAY, 
                LAG(LoginDate) OVER (PARTITION BY UserId ORDER BY LoginDate),
                LoginDate
            ) > 1 
            THEN 1 
            ELSE 0 
        END AS IslandStart
    FROM UserLogins
),
IslandGroups AS (
    SELECT 
        UserId,
        LoginDate,
        -- Create island groups
        SUM(IslandStart) OVER (
            PARTITION BY UserId 
            ORDER BY LoginDate
        ) AS IslandId
    FROM DateEvents
)
SELECT 
    UserId,
    MIN(LoginDate) AS IslandStartDate,
    MAX(LoginDate) AS IslandEndDate,
    DATEDIFF(DAY, MIN(LoginDate), MAX(LoginDate)) + 1 AS IslandLengthDays,
    COUNT(*) AS LoginsInIsland
FROM IslandGroups
GROUP BY UserId, IslandId
HAVING COUNT(*) >= 3  -- Only islands with 3+ consecutive logins
ORDER BY UserId, IslandStartDate;

-- Result: Identifies streaks of consecutive daily logins

Related posts:

How to create Local and Global Temp Tables in MS SQL

SQL: Fixing Slow Queries with Index Rebuilding

Covering Indexes Prevent Lookups

Post Views: 3

Post navigation

Ajax Revolution: How Fetch API and Async/Await Replace jQuery.ajax()
Git Hooks Automation: Pre-commit Checks That Save Hours of Debugging

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com