Approach: compute revenue per channel for the last two complete ISO weeks, calculate absolute and percent week-over-week (WoW) change, then if total revenue across channels decreased, return the top 5 channels with the largest negative contributions to that overall drop.SQL (Postgres syntax — adjust date functions for your dialect):sql
WITH params AS (
-- define "today" and compute last two complete ISO weeks
SELECT current_date AS today,
date_trunc('week', current_date)::date - interval '7 days' AS last_week_start,
date_trunc('week', current_date)::date - interval '14 days' AS prev_week_start
),
events_clean AS (
SELECT
user_id,
event_type,
COALESCE(amount, 0)::numeric AS amount, -- treat null amounts as 0
channel,
occurred_at::date AS occurred_at
FROM events
WHERE event_type = 'purchase' -- assume only purchases count as revenue
),
weekly_rev AS (
SELECT
e.channel,
CASE
WHEN e.occurred_at >= p.last_week_start AND e.occurred_at < p.last_week_start + interval '7 days' THEN 'last_week'
WHEN e.occurred_at >= p.prev_week_start AND e.occurred_at < p.prev_week_start + interval '7 days' THEN 'prev_week'
ELSE NULL
END AS week_label,
SUM(e.amount) AS revenue
FROM events_clean e
CROSS JOIN params p
WHERE e.occurred_at >= p.prev_week_start
AND e.occurred_at < p.last_week_start + interval '7 days'
GROUP BY 1,2
),
pivot AS (
SELECT
channel,
COALESCE(MAX(CASE WHEN week_label='prev_week' THEN revenue END),0) AS prev_revenue,
COALESCE(MAX(CASE WHEN week_label='last_week' THEN revenue END),0) AS last_revenue
FROM weekly_rev
GROUP BY channel
),
channel_changes AS (
SELECT
channel,
prev_revenue,
last_revenue,
last_revenue - prev_revenue AS delta,
CASE WHEN prev_revenue = 0 THEN NULL ELSE (last_revenue - prev_revenue)/prev_revenue END AS pct_change
FROM pivot
),
overall AS (
SELECT SUM(prev_revenue) AS total_prev, SUM(last_revenue) AS total_last FROM pivot
)
SELECT
cc.channel,
cc.prev_revenue,
cc.last_revenue,
cc.delta,
cc.pct_change
FROM channel_changes cc
CROSS JOIN overall o
WHERE o.total_last < o.total_prev -- only when overall revenue dropped
ORDER BY cc.delta ASC -- most negative contributions first
LIMIT 5;
Key assumptions and notes:- Attribution window: revenue is attributed to the event's channel at time of purchase (same-day attribution). If you need last-touch/windowed attribution, join conversion events to prior channel exposures within a time window.- Null amounts: treat NULL as 0 using COALESCE; alternatively, filter them out if representing missing data.- Users in multiple channels: this query attributes revenue to the channel recorded on the purchase event (per-event attribution). If you need user-level deduplication or multi-touch credit, implement sessionization or a multi-touch model and aggregate accordingly.- Filters: I limited to event_type='purchase'; adjust if revenue recorded differently.- Edge cases: channels with zero prev_revenue yield NULL pct_change; consider business rule to display as 100% or N/A.