Rider Retention Analysis
For riders who have taken at least 2 trips, what is the average number of days between consecutive trips?
- CTEs
- Window functions
- Joins
- Subqueries
- Aggregation
Challenge brief
Understand the request
Rider Retention Team is building a churn prediction model and needs the average gap between consecutive rides for returning riders.
Calculate inter-trip gap days per rider using LAG() window function and two CTEs.
Return
- rider_name (full name)
- total_trips
- avg_gap_days (rounded 1 decimal)
Constraints
- Use only completed trips
- Return riders with at least two completed trips
- Measure gaps between consecutive trips for the same rider
- Order by average gap ascending, then rider ID
Data you will use
Review the relevant tables before deciding how to join, filter, or aggregate them.
trips
trip_idINTEGERrider_idINTEGERpickup_datetimeDATETIMEtrip_statusVARCHAR(20)
riders
rider_idINTEGERfirst_nameVARCHAR(50)last_nameVARCHAR(50)
Hints, when you need them
Open one clue at a time so you still do the reasoning.
Hint 1
LAG(pickup_datetime) OVER (PARTITION BY rider_id ORDER BY pickup_datetime) gives each trip row the previous trip's timestamp for that rider. Subtract using julianday() for the gap in days. Then aggregate in a second CTE.
Hint 2
CTE trip_gaps: SELECT rider_id, pickup_datetime, LAG(pickup_datetime) OVER (PARTITION BY rider_id ORDER BY pickup_datetime) AS prev_trip_date, julianday(pickup_datetime) - julianday(LAG(...)) AS gap_days FROM trips WHERE completed. CTE rider_gap_stats: WHERE prev IS NOT NULL, AVG gap, COUNT(*)+1. Main: JOIN riders, WHERE total_trips >= 2.
Hint 3
Build one stage for prior-trip timestamps and another for rider-level gap averages, keeping only completed trip sequences.
Verified SQL answer
Attempt the problem first, then compare structure and reasoning—not just syntax.
Reveal solution and explanation
WITH trip_gaps AS (SELECT rider_id, pickup_datetime, LAG(pickup_datetime) OVER (PARTITION BY rider_id ORDER BY pickup_datetime, trip_id) AS prev_trip_date, julianday(pickup_datetime) - julianday(LAG(pickup_datetime) OVER (PARTITION BY rider_id ORDER BY pickup_datetime, trip_id)) AS gap_days FROM trips WHERE trip_status = 'completed'), rider_gap_stats AS (SELECT rider_id, COUNT(*) + 1 AS total_trips, ROUND(AVG(gap_days), 1) AS avg_gap_days FROM trip_gaps WHERE prev_trip_date IS NOT NULL GROUP BY rider_id) SELECT r.first_name || ' ' || r.last_name AS rider_name, rgs.total_trips, rgs.avg_gap_days FROM rider_gap_stats rgs INNER JOIN riders r ON rgs.rider_id = r.rider_id WHERE rgs.total_trips >= 2 ORDER BY rgs.avg_gap_days ASC, rgs.rider_idWhy this works
Only Alice Anderson (rider_id 101) has 2 trips — Jan 15 and Jan 16 — giving a 1.2-day gap. COUNT(*)+1 compensates: 2 trips produce 1 gap row, so +1 restores the trip count. LAG's first row has no previous trip and is NULL-filtered.
Success check
1 rider — Alice Anderson (2 trips, 1.2 day avg gap)
Expected result
Use this output to verify values, aliases, ordering, and row count.
| rider_name | total_trips | avg_gap_days |
|---|---|---|
| Alice Anderson | 2 | 1.2 |
Learn the concepts behind this answer
Strengthen your understanding with these targeted learning topics:
Continue practicing
Explore related company challenges
Airbnb
Independent Airbnb-style marketplace, booking, listing, payment, review, and guest analytics SQL practice.
Amazon
Independent Amazon-style e-commerce, warehouse, inventory, and customer analytics SQL practice.
Google
Independent Google-style search, advertising, user-engagement, and video-product SQL practice.
Return to the complete interview preparation experience.