Approach: compute each customer's revenue within 12 months after their signup (include transactions and subscription starts), then aggregate by acquisition channel to get average 12‑month LTV. Compute CAC by summing marketing_spend for the same signup-months (or a chosen attribution window) and dividing by number of customers acquired in that period. Assumptions listed after query.sql
WITH cust AS (
SELECT
id AS customer_id,
acquired_via AS channel,
signup_date,
signup_date + INTERVAL '12 months' AS signup_12m
FROM customers
),
-- revenue from transactions in the 12 months after signup
tx AS (
SELECT
c.customer_id,
c.channel,
SUM(t.amount) AS tx_revenue
FROM cust c
JOIN transactions t
ON t.customer_id = c.customer_id
AND t.occurred_at >= c.signup_date
AND t.occurred_at < c.signup_12m
GROUP BY c.customer_id, c.channel
),
-- revenue from subscriptions that start within 12 months (use price at start)
subs AS (
SELECT
c.customer_id,
c.channel,
SUM(s.price) AS subs_revenue
FROM cust c
JOIN subscriptions s
ON s.customer_id = c.customer_id
AND s.start_date >= c.signup_date
AND s.start_date < c.signup_12m
GROUP BY c.customer_id, c.channel
),
-- per-customer 12m revenue (coalesce to 0 when no rows)
cust_revenue AS (
SELECT
c.customer_id,
c.channel,
COALESCE(t.tx_revenue, 0) + COALESCE(s.subs_revenue, 0) AS revenue_12m
FROM cust c
LEFT JOIN tx t USING (customer_id, channel)
LEFT JOIN subs s USING (customer_id, channel)
),
-- per-channel aggregates: avg LTV and count of acquired customers in the period
channel_customers AS (
SELECT
channel,
COUNT(*) AS num_customers,
AVG(revenue_12m) AS avg_ltv,
SUM(revenue_12m) AS total_revenue
FROM cust_revenue
GROUP BY channel
),
-- marketing spend aggregated by channel over months corresponding to customer signups.
-- We map signup_date to month for attribution; adjust as needed.
channel_spend AS (
SELECT
m.channel,
SUM(m.amount) AS total_spend
FROM marketing_spend m
-- restrict spend to months where there were signups (optional)
JOIN (
SELECT DISTINCT channel, DATE_TRUNC('month', signup_date) AS month
FROM customers
) cs ON cs.channel = m.channel AND cs.month = m.month
GROUP BY m.channel
)
-- final: combine LTV and CAC
SELECT
cc.channel,
cc.num_customers,
cc.avg_ltv,
cc.total_revenue,
COALESCE(cs.total_spend, 0) AS total_spend,
CASE WHEN cc.num_customers > 0 THEN COALESCE(cs.total_spend,0)::numeric / cc.num_customers ELSE NULL END AS avg_cac
FROM channel_customers cc
LEFT JOIN channel_spend cs USING (channel)
ORDER BY cc.total_revenue DESC;
Key points / assumptions:- LTV window = 12 months after each customer's signup (changeable).- Subscriptions counted by start_date price; for recurring billing you'd prorate or sum invoices instead.- CAC attribution uses marketing_spend aggregated by channel-month matching signup months—modify if you need different attribution (e.g., lookback windows, multi-touch).- Edge cases: customers with no transactions/subscriptions get zero revenue; channels with zero customers yield NULL CAC; ensure timezone/ date formats align.