This lesson on Calculating LTV in SQL — Patterns from Real SaaS is hands-on and example-driven. You will calculate Customer Lifetime Value (CLV/LTV) and segment customer accounts using transactional sales data in SQL Server. By aggregating order frequency, average order value, and account lifespan into structured CTEs, you can classify users into distinct profitability tiers for targeted retention.
What You'll Be Able To Do
- Join customer dimension tables with transaction order headers to aggregate purchase history per user.
- Calculate core LTV components including Average Order Value (AOV), purchase frequency, and account lifespan in days and years.
- Derive estimated Customer Lifetime Value using CTE-based mathematical modeling across transactional tables.
- Implement conditional tiering logic with CASE statements to segment customers into actionable profitability buckets.
Detailed Concept Walkthrough
1. Customer Transaction Aggregation via CTEs
Before calculating advanced metrics like CLV, raw transactional order lines must be aggregated to the customer level. A Common Table Expression (CTE) consolidates individual order amounts, order counts, and earliest purchase dates per customer.
- Mechanism: Join
Sales.CustomertoSales.SalesOrderHeaderonCustomerIDto aggregate individual orders per account. This ensures every transaction is mapped back to an identified entity. - Aggregations Applied:
COUNT(SalesOrderID)yields purchase frequency,AVG(TotalDue)produces Average Order Value (AOV),SUM(TotalDue)provides historical spend, andMIN(OrderDate)captures the customer's acquisition date. - Lifespan Calculation: Compute raw account lifespan using
DATEDIFF(day, MIN(OrderDate), GETDATE()). This represents elapsed days since the customer's first purchase. - Execution Flow: Encapsulating this initial aggregation inside a CTE isolates grouped logic from subsequent mathematical derivations and segmentation rules.
WITH CustomerBaseMetrics AS (
SELECT
c.CustomerID,
COUNT(soh.SalesOrderID) AS PurchaseFrequency,
SUM(soh.TotalDue) AS TotalSales,
AVG(soh.TotalDue) AS AverageOrderValue,
DATEDIFF(day, MIN(soh.OrderDate), GETDATE()) AS DaysSinceFirstPurchase
FROM Sales.Customer c
INNER JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
GROUP BY c.CustomerID
)
SELECT * FROM CustomerBaseMetrics;
Key Takeaway: Group transaction headers by customer ID in a CTE to construct baseline metrics before attempting compound CLV calculations.
2. Calculating Customer Lifetime Value (CLV)
CLV models the total projected revenue an organization expects from a customer over their entire relationship. It combines spending size, transaction frequency, and relationship duration.
- Mathematical Formula: The standard formula applied is
CLV = Average Order Value * Purchase Frequency * Lifespan in Years. Lifespan in years is derived by dividing elapsed days by365.0. - Precision Nuance: When converting days to years, divide by a float (e.g.,
365.0) rather than an integer to prevent integer truncation during calculation. - Business Interpretation: This metric estimates long-term revenue contribution, allowing teams to determine how much budget can be allocated toward acquisition and retention.
WITH CustomerBaseMetrics AS (
SELECT
c.CustomerID,
COUNT(soh.SalesOrderID) AS PurchaseFrequency,
SUM(soh.TotalDue) AS TotalSales,
AVG(soh.TotalDue) AS AverageOrderValue,
DATEDIFF(day, MIN(soh.OrderDate), GETDATE()) AS DaysSinceFirstPurchase
FROM Sales.Customer c
INNER JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
GROUP BY c.CustomerID
)
SELECT
CustomerID,
PurchaseFrequency,
TotalSales,
AverageOrderValue,
DaysSinceFirstPurchase,
-- CLV = AOV * Frequency * (Days / 365.0)
(AverageOrderValue * PurchaseFrequency * (DaysSinceFirstPurchase / 365.0)) AS CLV
FROM CustomerBaseMetrics;
Key Takeaway: Derive CLV by scaling AOV and purchase frequency against normalized account lifespan in years.
3. Conditional Customer Segmentation
Segmenting customers by CLV boundaries categorizes accounts based on whether their lifetime value offsets acquisition and servicing costs. This guides where marketing and retention teams focus budget.
- Threshold Evaluation: Apply a
CASEexpression against the computed CLV metric to classify accounts into non-profitable, profitable, and highly profitable buckets. - Segment Definitions: CLV under 1,000 indicates non-profitable accounts failing to cover acquisition/service costs; 1,000 to 5,000 marks profitable moderate accounts; greater than 5,000 defines highly profitable high-value accounts.
- Strategic Action: High-value tiers receive dedicated retention efforts, while lower tiers trigger automated or low-cost marketing campaigns.
WITH CustomerCLV AS (
SELECT
c.CustomerID,
COUNT(soh.SalesOrderID) AS PurchaseFrequency,
SUM(soh.TotalDue) AS TotalSales,
AVG(soh.TotalDue) AS AverageOrderValue,
DATEDIFF(day, MIN(soh.OrderDate), GETDATE()) AS DaysSinceFirstPurchase,
(AVG(soh.TotalDue) * COUNT(soh.SalesOrderID) * (DATEDIFF(day, MIN(soh.OrderDate), GETDATE()) / 365.0)) AS CLV
FROM Sales.Customer c
INNER JOIN Sales.SalesOrderHeader soh
ON c.CustomerID = soh.CustomerID
GROUP BY c.CustomerID
)
SELECT
CustomerID,
CLV,
CASE
WHEN CLV < 1000 THEN 'Non-Profitable'
WHEN CLV BETWEEN 1000 AND 5000 THEN 'Profitable'
WHEN CLV > 5000 THEN 'Very Profitable'
END AS CustomerSegment
FROM CustomerCLV;
Key Takeaway: Use CASE expressions on CLV thresholds to bucket customer accounts into distinct profitability tiers.
Topics Covered in Calculating LTV in SQL — Patterns from Real SaaS
- CLV Overview (0:00 - 0:45) — The instructor outlines customer segmentation goals and defines core metrics using AdventureWorks 2019.
- Schema Examination (0:45 - 1:40) — Key columns across the Customer and SalesOrderHeader tables are reviewed for joins and aggregations.
- Aggregating Metrics (1:40 - 2:30) — A query is built to group orders by customer ID, calculating order counts, AOV, and lifespan.
- Applying CLV Formula (2:30 - 3:05) — Base metrics inside a CTE are multiplied together to calculate projected customer lifetime value.
- Profitability Segmentation (3:05 - 3:45) — A CASE expression segments customers into non-profitable, profitable, and very profitable tiers.
SQL Cheat Sheet
-
DATEDIFF(day, MIN(OrderDate), GETDATE())— Calculates elapsed days between first order and current dateDATEDIFF(day, MIN(OrderDate), GETDATE()) AS DaysSinceFirstPurchase -
AOV * Frequency * (Days / 365.0)— Computes estimated customer lifetime value in currency units(AVG(TotalDue) * COUNT(SalesOrderID) * (DaysSinceFirstPurchase / 365.0)) AS CLV -
CASE WHEN CLV < 1000 THEN ... END— Assigns customer profitability tiers based on value thresholdsCASE WHEN CLV < 1000 THEN 'Non-Profitable' WHEN CLV <= 5000 THEN 'Profitable' ELSE 'Very Profitable' END -
AVG(TotalDue)— Calculates Average Order Value per grouped entityAVG(soh.TotalDue) AS AverageOrderValue -
COUNT(SalesOrderID)— Counts total completed orders to establish purchase frequencyCOUNT(soh.SalesOrderID) AS PurchaseFrequency
Comparison Table
| Customer Segment | CLV Threshold | Business Characterization |
|---|---|---|
| Non-Profitable | < 1,000 | Does not cover acquisition costs |
| Profitable | 1,000 – 5,000 | Moderate engagement exceeding acquisition costs |
| Very Profitable | > 5,000 | High-frequency, high-spend core revenue drivers |
Common Pitfalls
- Mistake: Dividing lifespan days by integer 365 instead of a float. Avoid: Divide by 365.0 to prevent integer truncation in SQL Server.
- Mistake: Calculating CLV directly in the aggregation query without a CTE. Avoid: Aggregate base metrics in a CTE before applying multi-variable formulas.
- Mistake: Filtering on profile attributes like customer names instead of CustomerID. Avoid: Group and join exclusively on indexed unique keys like CustomerID.
FAQs
- Why is lifespan calculated from the first purchase date instead of account creation date? Order history measures active purchasing behavior, so the first order date accurately reflects the transactional lifespan start.
- Why are customers with CLV below 1,000 classified as non-profitable? Low lifetime spend often fails to cover the combined cost of customer acquisition (CAC) and account servicing.
- What table provides the financial total for orders in AdventureWorks?
The
Sales.SalesOrderHeadertable contains theTotalDuecolumn, which stores the complete amount owed per order.