E-Commerce Revenue & Customer Segmentation
End-to-end SQL analysis of a Brazilian e-commerce marketplace: revenue trends, RFM customer segmentation, and the measurable link between late deliveries and bad reviews.
View code & data on GitHub ↗The Problem
Marketplaces live and die by repeat purchases, yet most sellers only look at top-line revenue. The goal of this analysis was to answer three questions the revenue chart alone cannot: which customers are actually worth retaining, what share of revenue depends on a small group of heavy buyers, and whether operational failures like late deliveries are quietly destroying review scores and repeat-purchase intent.
The dataset is a real anonymized extract from Olist, spanning orders, payments, reviews, products, sellers, and geolocation across 9 normalized tables. That makes it a realistic playground for production-style SQL: multi-table joins, deduplication, and window functions rather than single-table toy queries.
Approach
Schema mapping & data audit
Documented the 9-table relational schema, verified primary and foreign keys, and audited for duplicate order rows, null delivery timestamps, and orders without payments before any aggregation.
Revenue & order trend base layer
Built a monthly revenue CTE joining orders to payments, filtering to delivered orders only, to establish trend, average order value, and category-level revenue mix.
RFM segmentation with window functions
Scored every customer on Recency, Frequency, and Monetary value using NTILE(5) window functions, then bucketed customers into named segments (Champions, Loyal, At Risk, Hibernating).
Delivery performance vs. review score
Computed actual vs. estimated delivery gaps per order and cross-tabulated lateness buckets against 1-5 star review scores to quantify the operational impact on customer satisfaction.
Cohort retention view
Grouped customers by first-purchase month and tracked repeat purchase behavior across subsequent months to expose the marketplace's structurally low repeat rate.
The Work
Representative excerpts from the analysis: the queries and transformations doing the heavy lifting. The full runnable project lives in the GitHub repo.
WITH customer_rfm AS (
SELECT
c.customer_unique_id,
MAX(o.order_purchase_timestamp) AS last_purchase,
COUNT(DISTINCT o.order_id) AS frequency,
SUM(p.payment_value) AS monetary
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
JOIN order_payments p ON p.order_id = o.order_id
WHERE o.order_status = 'delivered'
GROUP BY c.customer_unique_id
),
scored AS (
SELECT *,
NTILE(5) OVER (ORDER BY last_purchase) AS r_score,
NTILE(5) OVER (ORDER BY frequency) AS f_score,
NTILE(5) OVER (ORDER BY monetary) AS m_score
FROM customer_rfm
)
SELECT
CASE
WHEN r_score >= 4 AND f_score >= 4 THEN 'Champions'
WHEN r_score >= 4 AND f_score <= 2 THEN 'New / Promising'
WHEN r_score <= 2 AND f_score >= 3 THEN 'At Risk'
WHEN r_score <= 2 AND f_score <= 2 THEN 'Hibernating'
ELSE 'Loyal / Regular'
END AS segment,
COUNT(*) AS customers,
ROUND(SUM(monetary)::numeric, 0) AS revenue,
ROUND(AVG(monetary)::numeric, 2) AS avg_customer_value
FROM scored
GROUP BY 1
ORDER BY revenue DESC;WITH delivery_gap AS (
SELECT
o.order_id,
r.review_score,
o.order_delivered_customer_date::date
- o.order_estimated_delivery_date::date AS days_late
FROM orders o
JOIN order_reviews r ON r.order_id = o.order_id
WHERE o.order_status = 'delivered'
AND o.order_delivered_customer_date IS NOT NULL
)
SELECT
CASE
WHEN days_late <= 0 THEN 'On time / early'
WHEN days_late <= 3 THEN '1-3 days late'
WHEN days_late <= 7 THEN '4-7 days late'
ELSE '8+ days late'
END AS delivery_bucket,
COUNT(*) AS orders,
ROUND(AVG(review_score), 2) AS avg_review,
ROUND(100.0 * AVG(CASE WHEN review_score <= 2
THEN 1 ELSE 0 END), 1) AS pct_1_2_star
FROM delivery_gap
GROUP BY 1
ORDER BY MIN(days_late);What the Data Shows
Average review score by delivery timing
stars (out of 5)
Every day of lateness costs stars. Orders 8+ days late average 1.7 stars, and over 60% of them end in a 1 or 2 star review.
Key Findings
Roughly 93% of revenue comes from one-time buyers. The repeat purchase rate is only about 3%, meaning the marketplace effectively re-acquires its customer base every quarter.
The top RFM segments (Champions plus Loyal, about 19% of customers) account for a disproportionate 46% of total payment value, making retention offers to this group the highest-leverage move available.
On-time orders average 4.3 stars. Orders 8+ days late collapse to 1.7 stars, with over 60% of them receiving a 1 or 2 star review.
About 8% of delivered orders arrived after the promised date, concentrated in a handful of origin-destination state pairs. That is a logistics problem, not a seller-quality problem.
Average order value stayed flat (around R$160) while order volume grew, indicating growth came entirely from acquisition rather than basket expansion.
Recommendations
Retention program for the top quintile
Target Champions and Loyal segments (19% of customers, 46% of revenue) with free-shipping vouchers. Even a 1pp lift in repeat rate outweighs broad discounting.
Fix the worst delivery lanes first
Late deliveries cluster in specific interstate routes. Renegotiating or rerouting the 5 worst lanes attacks most of the 1-star review volume at its source.
Set expectations honestly
Orders delivered early rate 4.3+ stars. Padding estimated delivery dates by 2 days on risky lanes converts late orders into on-time ones at zero logistics cost.
Bundle to raise AOV
Flat AOV suggests cross-sell potential is untapped. Category-pair analysis (bed & bath with furniture) identifies natural bundle candidates.