HomeProjectsCertificationsContact
SQL

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.

DatasetOlist Brazilian E-Commerce (public, Kaggle)
Scale~100k orders · 9 relational tables · 2016–2018
ToolsPostgreSQL · CTEs · Window Functions · RFM Segmentation
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.

~100korders analyzed
9relational tables joined
46%revenue from top 19% customers
-2.6★review drop when 8+ days late

Approach

01

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.

02

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.

03

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).

04

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.

05

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.

RFM segmentation with NTILE window functionssql
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;
Late delivery impact on review scoressql
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)

On time / early
4.3
1-3 days late
3.6
4-7 days late
2.8
8+ days late
1.7

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.