E-commerce · Customer retention
What separates one-time buyers from repeat customers, and where should Olist focus to get more second purchases?
Olist is a Brazilian marketplace that connects small sellers with customers. In sales, I learned that winning a second order from a customer is usually cheaper than finding a new one. So before looking at anything else, I wanted to know how many Olist customers actually come back, and whether the ones who do are worth chasing.
| Orders placed | Customers | % of customers |
|---|---|---|
| 1 | 90,557 | 97.00% |
| 2 | 2,573 | 2.76% |
| 3 | 181 | 0.19% |
| 4 or more | 47 | 0.05% |
Only 2,801 customers ordered more than once during the period in the data.
WITH customer_orders AS (
SELECT c.customer_unique_id,
COUNT(DISTINCT o.order_id) AS order_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_status = 'delivered'
GROUP BY c.customer_unique_id
)
SELECT order_count,
COUNT(*) AS number_of_customers,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS pct_of_customers
FROM customer_orders
GROUP BY order_count
ORDER BY order_count;
| Customer type | Customers | Share of revenue | Average spend |
|---|---|---|---|
| One-time | 90,557 | 94.4% | R$160.74 |
| Repeat | 2,801 | 5.6% | R$308.53 |
Repeat customers spent about 92% more per person. Even a small increase in second purchases would matter.
Four of the top five categories were the same for both groups (health and beauty, home and bath, sports and leisure, computer accessories). Average review scores were almost identical too: 4.15 for one-time buyers and 4.21 for repeat buyers. So fixing products or service quality is unlikely to bring people back on its own.
| Purchase | Customers | Average days since previous |
|---|---|---|
| 2nd | 2,801 | 81.2 |
| 3rd | 228 | 62.8 |
| 4th | 47 | 60.6 |
| 5th | 19 | 61.2 |
The second purchase takes the longest, about 81 days. After that, gaps settle at around 61 to 63 days. Getting the second order is the hard part.
WITH customer_orders AS (
SELECT c.customer_unique_id, o.order_purchase_timestamp,
ROW_NUMBER() OVER (PARTITION BY c.customer_unique_id
ORDER BY o.order_purchase_timestamp) AS order_sequence,
LAG(o.order_purchase_timestamp) OVER (PARTITION BY c.customer_unique_id
ORDER BY o.order_purchase_timestamp) AS previous_order_date
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_status = 'delivered'
)
SELECT order_sequence,
COUNT(*) AS customers_at_this_order,
ROUND(AVG(DATEDIFF(order_purchase_timestamp, previous_order_date)), 1) AS avg_days
FROM customer_orders
WHERE order_sequence > 1
GROUP BY order_sequence
ORDER BY order_sequence
LIMIT 5;
| Seller location | Customers | Repeat rate |
|---|---|---|
| São José dos Campos, SP | 88 | 22.73% |
| São Paulo, SP | 58 | 22.41% |
| Porto Ferreira, SP | 68 | 17.65% |
| Platform average | 93,358 | 2.76% |
Among sellers with 50+ customers, the best kept up to 8 times more customers than the platform average. Location, category and reviews didn't explain it, so these sellers are worth studying.
Customers who bought near the end of the dataset had less time to come back, so tracking cohorts over the same time window would give a fairer picture. I'd also add delivery times, late deliveries and freight costs to see whether they explain some of the churn.
My first import stored IDs and dates as TEXT, so MySQL couldn't index them and my five-table joins kept timing out. I re-imported all 8 tables with the right data types (VARCHAR for IDs, DATETIME for dates), added 14 indexes, and the same query then ran in 8.13 seconds.
Open to remote Business Analyst, Data Analyst, BI and Sales or Revenue Operations roles. The fastest way to reach me is by email.