← Back to Metrics

Churned Customers

Also known as: Lapsed Customers, Lost Customers, Churned Buyers, Customer Churn Count, Inactive Customers

RetentionmetricE-commerce: Customer Retention, Loyalty & LTVE-commerce

Count of customers whose last purchase passed the inactivity window (default 365 days) during the period with no repeat order. Numerator of customer churn rate and an input to Customer Retention Rate.

Business Use Case

1️⃣ CRM Marketing sees churned customers rise in a cohort and starts a win-back offer for customers at 300 days inactive.,2️⃣ Finance reviews churned customers by value tier each quarter and cuts the repeat revenue assumed in the LTV forecast.,3️⃣ Merchandising checks the last-bought categories of churned customers and fills the range gaps they left over.

Business Criticality

High

Journey / Navigation Area

Engagement

Owning Function

Marketing

Scope

User

Formula

COUNT(DISTINCT customer_id) WHERE last_purchase_date + inactivity_window (365 days) falls in the reporting period AND no purchase between last_purchase_date and the churn date

Calculation Notes

1️⃣ A customer churns on the day their last purchase turns 365 days old and is counted once, in the period that contains that day.,2️⃣ The 365-day window is the default; fast-repeat ranges (grocery, consumables) set it to 2–3× the median days between orders.,3️⃣ Counts purchase events of identified, logged-in customers (customer_id = user_id); guest orders without a customer_id are excluded.,4️⃣ Fully cancelled or fully refunded orders do not reset the clock; partially refunded and exchanged orders still count as purchases.,5️⃣ A churned customer who buys again is reactivated from that order; churn already reported for the earlier period is not restated.,6️⃣ Test, employee and reseller accounts are excluded. Subscription cancellations belong to Subscription Renewal Rate, not this count.

Technical Details

Measure TypeCounter
Aggregation WindowMonthly, Quarterly, Yearly

Events

PlatformEvent
Google Analytics 4purchase
Adobepurchase

Data Mappings

W3 Data Layer
{
  "digitalData": {
    "transaction": {
      "transactionID": "txn-1",
      "total": {
        "transactionTotal": 100,
        "currency": "USD"
      }
    },
    "user": [
      {
        "profile": [
          {
            "profileInfo": {
              "profileID": "user-1"
            },
            "attributes": {
              "accountType": "consumer"
            }
          }
        ]
      }
    ],
    "event": [
      {
        "eventInfo": {
          "eventName": "purchase"
        }
      },
      {
        "eventInfo": {
          "eventName": "refund"
        },
        "attributes": {
          "transactionID": "txn-1",
          "value": 100,
          "currency": "USD"
        }
      }
    ]
  }
}
GA4 Data Layer
[
  {
    "event": "purchase",
    "ecommerce": {
      "transaction_id": "txn-1",
      "value": 100,
      "currency": "USD",
      "items": [
        {
          "item_id": "SKU-1",
          "item_name": "item",
          "price": 100,
          "quantity": 1
        }
      ]
    },
    "user_id": "user-1",
    "account_type": "consumer"
  },
  {
    "event": "refund",
    "ecommerce": {
      "transaction_id": "txn-1",
      "value": 100,
      "currency": "USD"
    },
    "user_id": "user-1"
  }
]
Adobe Client Data Layer
[
  {
    "event": "purchase",
    "purchase": {
      "transactionID": "txn-1",
      "value": 100,
      "currency": "USD",
      "items": [
        {
          "itemId": "SKU-1",
          "itemName": "item",
          "price": 100,
          "quantity": 1
        }
      ]
    },
    "user": {
      "id": "user-1",
      "accountType": "consumer"
    }
  },
  {
    "event": "refund",
    "refund": {
      "transactionID": "txn-1",
      "value": 100,
      "currency": "USD"
    },
    "user": {
      "id": "user-1"
    }
  }
]
XDM Mapping
[
  {
    "eventType": "commerce.purchases",
    "commerce": {
      "order": {
        "purchaseID": "txn-1",
        "priceTotal": 100,
        "currencyCode": "USD"
      },
      "purchases": {
        "value": 1
      }
    },
    "identityMap": {
      "customerID": [
        {
          "id": "user-1",
          "authenticatedState": "authenticated",
          "primary": true
        }
      ]
    },
    "_tenantId": {
      "accountType": "consumer"
    }
  },
  {
    "eventType": "refund",
    "commerce": {
      "order": {
        "purchaseID": "txn-1",
        "priceTotal": 100,
        "currencyCode": "USD"
      }
    },
    "identityMap": {
      "customerID": [
        {
          "id": "user-1",
          "authenticatedState": "authenticated",
          "primary": true
        }
      ]
    }
  }
]

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
DECLARE start_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY);
DECLARE end_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY);
DECLARE inactivity_days INT64 DEFAULT 365;

WITH
-- Purchases per transaction (double-fires collapsed), identified logged-in customers only; guest orders without user_id excluded
purchases AS (
  SELECT
    ecommerce.transaction_id AS transaction_id,
    ANY_VALUE(user_id) AS customer_id,
    MIN(PARSE_DATE('%Y%m%d', event_date)) AS purchase_date,
    MAX(ecommerce.purchase_revenue) AS order_value,
    LOGICAL_OR((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'account_type') IN ('test', 'employee', 'reseller')) AS is_excluded_account
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(start_date, INTERVAL inactivity_days DAY)) AND FORMAT_DATE('%Y%m%d', end_date)
    AND event_name = 'purchase'
    AND user_id IS NOT NULL
    AND ecommerce.transaction_id IS NOT NULL
  GROUP BY transaction_id
),
-- Refunded amount per transaction (cancellations arrive as full refunds)
refunds AS (
  SELECT
    ecommerce.transaction_id AS transaction_id,
    SUM(ecommerce.refund_value) AS refunded_value
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(start_date, INTERVAL inactivity_days DAY)) AND FORMAT_DATE('%Y%m%d', end_date)
    AND event_name = 'refund'
  GROUP BY transaction_id
),
-- Purchase days that reset the clock: fully refunded orders and test, employee and reseller accounts removed
purchase_days AS (
  SELECT DISTINCT p.customer_id, p.purchase_date
  FROM purchases p
  LEFT JOIN refunds r USING (transaction_id)
  WHERE (r.transaction_id IS NULL OR r.refunded_value < p.order_value)
    AND p.customer_id NOT IN (SELECT customer_id FROM purchases WHERE is_excluded_account)
),
-- Churn date of each purchase and the customer's next purchase, if any
lapses AS (
  SELECT
    customer_id,
    DATE_ADD(purchase_date, INTERVAL inactivity_days DAY) AS churn_date,
    LEAD(purchase_date) OVER (PARTITION BY customer_id ORDER BY purchase_date) AS next_purchase_date
  FROM purchase_days
)
-- Customers whose purchase turned inactivity_days old in the period with no repeat order, counted once
SELECT COUNT(DISTINCT customer_id) AS churned_customers
FROM lapses
WHERE churn_date BETWEEN start_date AND end_date
  AND (next_purchase_date IS NULL OR next_purchase_date > churn_date);
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
WITH
-- Reporting period and inactivity window (relative dates)
params AS (
  SELECT
    date_sub(current_date(), 90) AS start_date,
    date_sub(current_date(), 1) AS end_date,
    365 AS inactivity_days
),
-- Purchases per transaction (double-fires collapsed), identified logged-in customers only; guest orders without customerID excluded
purchases AS (
  SELECT
    e.commerce.order.purchaseID AS transaction_id,
    MAX(e.identityMap['customerID'][0].id) AS customer_id,
    MIN(to_date(e.timestamp)) AS purchase_date,
    MAX(e.commerce.order.priceTotal) AS order_value,
    MAX(CASE WHEN e._tenantId.accountType IN ('test', 'employee', 'reseller') THEN 1 ELSE 0 END) AS is_excluded_account
  FROM aep_web_events_dataset e
  CROSS JOIN params p
  WHERE to_date(e.timestamp) BETWEEN date_sub(p.start_date, p.inactivity_days) AND p.end_date
    AND e.commerce.purchases.value = 1
    AND e.identityMap['customerID'][0].id IS NOT NULL
    AND e.commerce.order.purchaseID IS NOT NULL
  GROUP BY e.commerce.order.purchaseID
),
-- Refunded amount per transaction (cancellations arrive as full refunds)
refunds AS (
  SELECT
    e.commerce.order.purchaseID AS transaction_id,
    SUM(e.commerce.order.priceTotal) AS refunded_value
  FROM aep_web_events_dataset e
  CROSS JOIN params p
  WHERE to_date(e.timestamp) BETWEEN date_sub(p.start_date, p.inactivity_days) AND p.end_date
    AND e.eventType = 'refund'
  GROUP BY e.commerce.order.purchaseID
),
-- Purchase days that reset the clock: fully refunded orders and test, employee and reseller accounts removed
purchase_days AS (
  SELECT DISTINCT pu.customer_id, pu.purchase_date
  FROM purchases pu
  LEFT JOIN refunds r ON r.transaction_id = pu.transaction_id
  WHERE (r.transaction_id IS NULL OR r.refunded_value < pu.order_value)
    AND pu.customer_id NOT IN (SELECT customer_id FROM purchases WHERE is_excluded_account = 1)
),
-- Churn date of each purchase and the customer's next purchase, if any
lapses AS (
  SELECT
    d.customer_id,
    date_add(d.purchase_date, p.inactivity_days) AS churn_date,
    LEAD(d.purchase_date) OVER (PARTITION BY d.customer_id ORDER BY d.purchase_date) AS next_purchase_date
  FROM purchase_days d
  CROSS JOIN params p
)
-- Customers whose purchase turned inactivity_days old in the period with no repeat order, counted once
SELECT COUNT(DISTINCT l.customer_id) AS churned_customers
FROM lapses l
CROSS JOIN params p
WHERE l.churn_date BETWEEN p.start_date AND p.end_date
  AND (l.next_purchase_date IS NULL OR l.next_purchase_date > l.churn_date);

Source Data

Digital Analytics (GA4) + Adobe Analytics / AEP

Dependencies

Prerequisite: List the dependencies required for this Metric

Events:

purchaserefund-issued

Report Attributes

Acquisition Cohort Customer Value Tier Customer Loyalty Tier First Touch Channel Grouping Item Category

Recommended Reporting Views

Views
Customer Retention & ChurnCRM Lifecycle PerformanceCustomer Lifetime Value Overview

Segment Eligibility

true

Governance

Data SensitivityInternal

Derived KPIs

Governance

Created bydevyendarm
Created on9/29/2026
Last modified bydevyendarm
Last modified on9/29/2026
Version1.0.0
Validation stateqa passed
StatusPUBLISHED

GitHub

View related Pull Requestcatalog/metrics/churned-customers.yml

Community Discussion