Coding questions · Uber
Write SQL and Pandas for Uber Trips
This question evaluates SQL and Pandas data manipulation competencies, including joins, time-based filtering, handling NULLs, aggregations, rate and ratio calculations, and windowed comparisons over grouped city-day data.
Assume 'today' = 2025-09-01. You are given the following schema and small ASCII samples.
Tables
- riders(rider_id, name, signup_date)
- drivers(driver_id, name, car_year)
- trips(trip_id, rider_id, driver_id, request_time, pickup_time, dropoff_time, status, city, surge_multiplier, distance_km, fare_usd)
• status ∈ {'completed','cancelled_by_rider','cancelled_by_driver','driver_no_show'}
- payments(trip_id, amount_usd, method, card_hash, charge_time, success)
- devices(user_type, user_id, device_id) where user_type ∈ {'rider','driver'}
Samples (minimal, not exhaustive)
riders
rider_id | name | signup_date
1 | Alice | 2025-08-20
2 | Bob | 2025-08-28
3 | Chen | 2025-08-30
4 | Deepa | 2025-07-15
drivers
driver_id | name | car_year
10 | Diego | 2018
11 | Elena | 2021
12 | Farid | 2015
trips
trip_id | rider_id | driver_id | request_time | pickup_time | dropoff_time | status | city | surge_multiplier | distance_km | fare_usd
1 | 1 | 10 | 2025-08-25 19:55 | 2025-08-25 20:05 | 2025-08-25 20:25 | completed | SF | 1.8 | 8.0 | 24.5
2 | 2 | 11 | 2025-08-26 14:10 | 2025-08-26 14:15 | 2025-08-26 14:30 | completed | SF | 1.0 | 5.0 | 12.0
3 | 2 | 11 | 2025-08-26 20:05 | 2025-08-26 20:30 | NULL | cancelled_by_driver | SF | 2.0 | 0.0 | 0.0
4 | 3 | 12 | 2025-08-27 02:30 | NULL | NULL | cancelled_by_rider | NYC | 1.0 | 0.0 | 0.0
5 | 3 | 10 | 2025-08-30 20:15 | 2025-08-30 20:25 | 2025-08-30 20:45 | completed | NYC | 1.7 | 6.5 | 18.0
6 | 1 | 12 | 2025-08-31 14:05 | 2025-08-31 14:10 | 2025-08-31 14:30 | completed | SF | 1.0 | 7.0 | 16.0
7 | 4 | 10 | 2025-08-31 20:05 | 2025-08-31 20:20 | 2025-08-31 20:40 | completed | SF | 1.6 | 9.0 | 22.0
8 | 4 | 11 | 2025-09-01 02:10 | NULL | NULL | driver_no_show | SF | 1.0 | 0.0 | 0.0
payments
trip_id | amount_usd | method | card_hash | charge_time | success
1 | 24.5 | card | abc123 | 2025-08-25 20:26 | true
2 | 12.0 | card | xyz777 | 2025-08-26 14:31 | true
3 | 0.0 | card | xyz777 | 2025-08-26 20:31 | false
4 | 0.0 | card | qwe555 | 2025-08-27 02:32 | false
5 | 18.0 | card | abc123 | 2025-08-30 20:46 | true
6 | 16.0 | card | abc123 | 2025-08-31 14:31 | true
7 | 22.0 | cash | NULL | 2025-08-31 20:41 | false
8 | 0.0 | card | xyz777 | 2025-09-01 02:12 | false
devices
user_type | user_id | device_id
rider | 1 | devA
rider | 2 | devB
rider | 3 | devC
rider | 4 | devB
driver | 10 | devX
driver | 11 | devY
driver | 12 | devZ
Write SQL answers for A–D and a Pandas answer for E:
A) For the last 7 days (2025-08-26 to 2025-09-01 inclusive), compute per city: total requests, completed trips, and completion rate = completed / requests. Count a request if a row exists in trips (any status). Order by completion rate ascending. Handle NULL times robustly and ensure date filtering uses request_time in UTC.
B) For each driver, over the last 7 days, compute the ratio: avg surge during 20:00–21:59 divided by avg surge during 14:00–15:59 on their completed trips, within the same city-day buckets. Return drivers where both windows have at least 1 completed trip and the ratio > 1.5. Include driver_id, city, counts per window, both averages, and the ratio.
C) Over the last 7 days, compute the median pickup wait (pickup_time - request_time) per city for completed trips, after excluding trips above the city-specific 95th percentile wait. Use window functions to compute the percentile cutoff and the median on the truncated set.
D) Identify likely duplicate rider accounts in the last 30 days: output rider_id_a, rider_id_b (a<b), evidence_type ('device' or 'card'), evidence_value (device_id or card_hash), first_seen_time. A pair qualifies if the riders share either the same device_id in devices or the same payments.card_hash used on trips by different rider_ids. Exclude NULL evidence values. For 'card', join payments→trips to map card_hash to rider_id.
E) Pandas: Given a DataFrame trips_df of trips, compute 7-day new-user retention by cohort for riders whose first completed trip date is between 2025-08-25 and 2025-08-31. A rider is retained if they have ≥1 additional completed trip with dropoff_time within 7 days (inclusive) after their first completed trip. Return a DataFrame with cohort_date, n_new, n_retained, retention_rate, sorted by cohort_date.
City-level completion rate over a 7-day window
For the 7-day window FROM 2025-05-26 TO 2025-06-01 (inclusive), using request_time (UTC), compute per city: (1) total ride requests, (2) total completed trips, and (3) completion_rate = completed_trips / total_requests. Count a request if a row exists in trips with any status. Handle NULL pickup/dropoff times robustly (they should not affect counts). Order the result by completion_rate ascending.
Table riders
| column | type | constraints |
|---|
| rider_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| signup_date | DATE | NOT NULL |
Sample data:
| name | rider_id | signup_date |
|---|
| Alice | 1 | 2025-05-20 |
| Bob | 2 | 2025-05-28 |
| Chen | 3 | 2025-05-30 |
| Deepa | 4 | 2025-04-15 |
Table drivers
| column | type | constraints |
|---|
| driver_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| car_year | INT | NOT NULL |
Sample data:
| car_year | driver_id | name |
|---|
| 2018 | 10 | Diego |
| 2021 | 11 | Elena |
| 2015 | 12 | Farid |
Table trips
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| rider_id | INT | NOT NULL |
| driver_id | INT | NOT NULL |
| request_time | TIMESTAMP | NOT NULL |
| pickup_time | TIMESTAMP | |
| dropoff_time | TIMESTAMP | |
| status | VARCHAR(32) | NOT NULL |
| city | VARCHAR(50) | NOT NULL |
| surge_multiplier | DECIMAL(3,1) | NOT NULL |
| distance_km | DECIMAL(5,1) | NOT NULL |
| fare_usd | DECIMAL(6,2) | NOT NULL |
Sample data:
| city | distance_km | driver_id | dropoff_time | fare_usd | pickup_time | request_time | rider_id | status | surge_multiplier | trip_id |
|---|
| SF | 8 | 10 | 2025-05-25 20:25:00 | 24.5 | 2025-05-25 20:05:00 | 2025-05-25 19:55:00 | 1 | completed | 1.8 | 1 |
| SF | 5 | 11 | 2025-05-26 14:30:00 | 12 | 2025-05-26 14:15:00 | 2025-05-26 14:10:00 | 2 | completed | 1 | 2 |
| SF | 0 | 11 | NULL | 0 | 2025-05-26 20:30:00 | 2025-05-26 20:05:00 | 2 | cancelled_by_driver | 2 | 3 |
| NYC | 0 | 12 | NULL | 0 | NULL | 2025-05-27 02:30:00 | 3 | cancelled_by_rider | 1 | 4 |
| NYC | 6.5 | 10 | 2025-05-30 20:45:00 | 18 | 2025-05-30 20:25:00 | 2025-05-30 20:15:00 | 3 | completed | 1.7 | 5 |
| SF | 7 | 10 | 2025-05-31 14:30:00 | 16 | 2025-05-31 14:10:00 | 2025-05-31 14:05:00 | 1 | completed | 1 | 6 |
| SF | 9 | 10 | 2025-05-31 20:40:00 | 22 | 2025-05-31 20:20:00 | 2025-05-31 20:05:00 | 4 | completed | 1.6 | 7 |
| SF | 0 | 11 | NULL | 0 | NULL | 2025-06-01 02:10:00 | 4 | driver_no_show | 1 | 8 |
Table payments
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| amount_usd | DECIMAL(6,2) | NOT NULL |
| method | VARCHAR(20) | NOT NULL |
| card_hash | VARCHAR(100) | |
| charge_time | TIMESTAMP | NOT NULL |
| success | BOOLEAN | NOT NULL |
Sample data:
| amount_usd | card_hash | charge_time | method | success | trip_id |
|---|
| 24.5 | abc123 | 2025-05-25 20:26:00 | card | true | 1 |
| 12 | xyz777 | 2025-05-26 14:31:00 | card | true | 2 |
| 0 | xyz777 | 2025-05-26 20:31:00 | card | false | 3 |
| 0 | qwe555 | 2025-05-27 02:32:00 | card | false | 4 |
| 18 | abc123 | 2025-05-30 20:46:00 | card | true | 5 |
| 16 | abc123 | 2025-05-31 14:31:00 | card | true | 6 |
| 22 | NULL | 2025-05-31 20:41:00 | cash | false | 7 |
| 0 | xyz777 | 2025-06-01 02:12:00 | card | false | 8 |
Table devices
| column | type | constraints |
|---|
| user_type | VARCHAR(10) | NOT NULL |
| user_id | INT | NOT NULL |
| device_id | VARCHAR(50) | NOT NULL |
Sample data:
| device_id | user_id | user_type |
|---|
| devA | 1 | rider |
| devB | 2 | rider |
| devC | 3 | rider |
| devB | 4 | rider |
| devX | 10 | driver |
| devY | 11 | driver |
| devZ | 12 | driver |
Expected output
One row per city with total requests, total completed trips, and completion_rate over 2025-05-26 to 2025-06-01 based on request_time.
| city | completed_trips | completion_rate | total_requests |
|---|
| NYC | 1 | 0.5 | 2 |
| SF | 3 | 0.6 | 5 |
Hints
- Filter trips by request_time between '2025-05-26' and '2025-06-01' (inclusive) and group by city.
- Compute completion_rate as completed_trips * 1.0 / total_requests to avoid integer division.
Driver surge ratio between evening and afternoon windows
For each driver, over the 7-day window FROM 2025-05-26 TO 2025-06-01 (inclusive), consider only completed trips. For each driver, city, and service_date = DATE(request_time), compute:
- avg_surge_afternoon: average surge_multiplier for trips with request_time between 14:00 and 15:59.
- avg_surge_evening: average surge_multiplier for trips with request_time between 20:00 and 21:59.
Return rows where, within the same (driver_id, city, service_date) bucket, there is at least 1 completed trip in each window and the ratio avg_surge_evening / avg_surge_afternoon > 1.5.
Output columns: driver_id, city, service_date, afternoon_trip_count, evening_trip_count, avg_surge_afternoon, avg_surge_evening, surge_ratio. Use request_time (UTC) for time-of-day bucketing.
Table riders
| column | type | constraints |
|---|
| rider_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| signup_date | DATE | NOT NULL |
Sample data:
| name | rider_id | signup_date |
|---|
| Alice | 1 | 2025-05-20 |
| Bob | 2 | 2025-05-28 |
| Chen | 3 | 2025-05-30 |
| Deepa | 4 | 2025-04-15 |
Table drivers
| column | type | constraints |
|---|
| driver_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| car_year | INT | NOT NULL |
Sample data:
| car_year | driver_id | name |
|---|
| 2018 | 10 | Diego |
| 2021 | 11 | Elena |
| 2015 | 12 | Farid |
Table trips
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| rider_id | INT | NOT NULL |
| driver_id | INT | NOT NULL |
| request_time | TIMESTAMP | NOT NULL |
| pickup_time | TIMESTAMP | |
| dropoff_time | TIMESTAMP | |
| status | VARCHAR(32) | NOT NULL |
| city | VARCHAR(50) | NOT NULL |
| surge_multiplier | DECIMAL(3,1) | NOT NULL |
| distance_km | DECIMAL(5,1) | NOT NULL |
| fare_usd | DECIMAL(6,2) | NOT NULL |
Sample data:
| city | distance_km | driver_id | dropoff_time | fare_usd | pickup_time | request_time | rider_id | status | surge_multiplier | trip_id |
|---|
| SF | 8 | 10 | 2025-05-25 20:25:00 | 24.5 | 2025-05-25 20:05:00 | 2025-05-25 19:55:00 | 1 | completed | 1.8 | 1 |
| SF | 5 | 11 | 2025-05-26 14:30:00 | 12 | 2025-05-26 14:15:00 | 2025-05-26 14:10:00 | 2 | completed | 1 | 2 |
| SF | 0 | 11 | NULL | 0 | 2025-05-26 20:30:00 | 2025-05-26 20:05:00 | 2 | cancelled_by_driver | 2 | 3 |
| NYC | 0 | 12 | NULL | 0 | NULL | 2025-05-27 02:30:00 | 3 | cancelled_by_rider | 1 | 4 |
| NYC | 6.5 | 10 | 2025-05-30 20:45:00 | 18 | 2025-05-30 20:25:00 | 2025-05-30 20:15:00 | 3 | completed | 1.7 | 5 |
| SF | 7 | 10 | 2025-05-31 14:30:00 | 16 | 2025-05-31 14:10:00 | 2025-05-31 14:05:00 | 1 | completed | 1 | 6 |
| SF | 9 | 10 | 2025-05-31 20:40:00 | 22 | 2025-05-31 20:20:00 | 2025-05-31 20:05:00 | 4 | completed | 1.6 | 7 |
| SF | 0 | 11 | NULL | 0 | NULL | 2025-06-01 02:10:00 | 4 | driver_no_show | 1 | 8 |
Expected output
Rows for driver-city-service_date combinations in 2025-05-26 to 2025-06-01 where both afternoon and evening windows have at least one completed trip and the surge ratio > 1.5.
| afternoon_trip_count | avg_surge_afternoon | avg_surge_evening | city | driver_id | evening_trip_count | service_date | surge_ratio |
|---|
| 1 | 1 | 1.6 | SF | 10 | 1 | 2025-05-31 | 1.6 |
Hints
- Bucket completed trips into afternoon and evening using EXTRACT(HOUR FROM request_time) and conditional aggregates.
- Group by driver_id, city, and DATE(request_time), then filter to groups with counts in both windows and a surge ratio above 1.5.
Median pickup wait per city excluding 95th percentile outliers
Median pickup wait per city (excluding 95th-percentile outliers)
You are analyzing rider pickup waits from the trips table.
Consider only completed trips (status = 'completed') whose request_time falls within the 7-day window from 2025-05-26 to 2025-06-01 inclusive (i.e. request_time >= '2025-05-26 00:00:00' and request_time < '2025-06-02 00:00:00'). Ignore any trip with a NULL pickup_time.
For each qualifying trip, define the pickup wait time in minutes as (pickup_time - request_time) expressed in minutes.
Then, per city:
- Compute the 95th percentile of the wait time using a continuous percentile (
PERCENTILE_CONT(0.95)).
- Exclude any trip whose wait time is strictly greater than that city's 95th-percentile cutoff (i.e. keep trips where
wait_minutes <= p95).
- On the remaining (truncated) set, compute the median wait time using
PERCENTILE_CONT(0.5).
Output
Return one row per city with columns:
city — the city name
median_wait_minutes — the median pickup wait in minutes for that city after outlier removal, rounded to 2 decimal places
Sort the result by city ascending.
Table riders
| column | type | constraints |
|---|
| rider_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| signup_date | DATE | NOT NULL |
Sample data:
| name | rider_id | signup_date |
|---|
| Alice | 1 | 2025-05-20 |
| Bob | 2 | 2025-05-28 |
| Chen | 3 | 2025-05-30 |
| Deepa | 4 | 2025-04-15 |
Table drivers
| column | type | constraints |
|---|
| driver_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| car_year | INT | NOT NULL |
Sample data:
| car_year | driver_id | name |
|---|
| 2018 | 10 | Diego |
| 2021 | 11 | Elena |
| 2015 | 12 | Farid |
Table trips
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| rider_id | INT | NOT NULL |
| driver_id | INT | NOT NULL |
| request_time | TIMESTAMP | NOT NULL |
| pickup_time | TIMESTAMP | |
| dropoff_time | TIMESTAMP | |
| status | VARCHAR(32) | NOT NULL |
| city | VARCHAR(50) | NOT NULL |
| surge_multiplier | DECIMAL(3,1) | NOT NULL |
| distance_km | DECIMAL(5,1) | NOT NULL |
| fare_usd | DECIMAL(6,2) | NOT NULL |
Sample data:
| city | distance_km | driver_id | dropoff_time | fare_usd | pickup_time | request_time | rider_id | status | surge_multiplier | trip_id |
|---|
| SF | 8 | 10 | 2025-05-25 20:25:00 | 24.5 | 2025-05-25 20:05:00 | 2025-05-25 19:55:00 | 1 | completed | 1.8 | 1 |
| SF | 5 | 11 | 2025-05-26 14:30:00 | 12 | 2025-05-26 14:15:00 | 2025-05-26 14:10:00 | 2 | completed | 1 | 2 |
| SF | 0 | 11 | NULL | 0 | 2025-05-26 20:30:00 | 2025-05-26 20:05:00 | 2 | cancelled_by_driver | 2 | 3 |
| NYC | 0 | 12 | NULL | 0 | NULL | 2025-05-27 02:30:00 | 3 | cancelled_by_rider | 1 | 4 |
| NYC | 6.5 | 10 | 2025-05-30 20:45:00 | 18 | 2025-05-30 20:25:00 | 2025-05-30 20:15:00 | 3 | completed | 1.7 | 5 |
| NYC | 10 | 11 | 2025-05-31 09:10:00 | 30 | 2025-05-31 08:50:00 | 2025-05-31 08:45:00 | 3 | completed | 1.2 | 9 |
| NYC | 14 | 12 | 2025-06-01 10:10:00 | 45 | 2025-06-01 09:40:00 | 2025-06-01 09:00:00 | 4 | completed | 2.5 | 10 |
| SF | 7 | 10 | 2025-05-31 14:30:00 | 16 | 2025-05-31 14:10:00 | 2025-05-31 14:05:00 | 1 | completed | 1 | 6 |
| SF | 9 | 10 | 2025-05-31 20:40:00 | 22 | 2025-05-31 20:20:00 | 2025-05-31 20:05:00 | 4 | completed | 1.6 | 7 |
| SF | 0 | 11 | NULL | 0 | NULL | 2025-06-01 02:10:00 | 4 | driver_no_show | 1 | 8 |
Expected output
One row per city with the median pickup wait (minutes, rounded to 2dp) over completed trips in the 7-day window, after dropping waits above each city's 95th percentile; sorted by city.
| city | median_wait_minutes |
|---|
| NYC | 7.50 |
| SF | 5.00 |
Hints
- Subtract the two timestamps and convert to minutes with EXTRACT(EPOCH FROM (pickup_time - request_time)) / 60.0.
- In PostgreSQL, PERCENTILE_CONT cannot be a window function (no OVER) — compute each city's p95 in a GROUP BY CTE and join it back to filter outliers.
- After dropping waits above the city p95, compute the median with PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY wait_minutes), grouped by city.
Detecting likely duplicate rider accounts via shared devices or cards
Identify likely duplicate rider accounts in the last 30 days, defined as FROM 2025-05-03 TO 2025-06-01 (inclusive). A rider pair (rider_id_a, rider_id_b) with rider_id_a < rider_id_b qualifies if:
- They share the same non-NULL device_id in the devices table (user_type = 'rider'), based on trips taken in that 30-day window; OR
- They share the same non-NULL payments.card_hash, where card_hash is mapped to rider_id via payments.trip_id -> trips.trip_id, and the payment charge_time is in that 30-day window.
For each qualifying pair and evidence source, output: rider_id_a, rider_id_b, evidence_type ('device' or 'card'), evidence_value (device_id or card_hash), and first_seen_time, defined as the earliest request_time (for device evidence) or charge_time (for card evidence) in the 30-day window where this shared identifier is observed for either rider.
Return one row per pair per evidence_type. Return first_seen_time formatted as YYYY-MM-DD HH24:MI:SS.
Table riders
| column | type | constraints |
|---|
| rider_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| signup_date | DATE | NOT NULL |
Sample data:
| name | rider_id | signup_date |
|---|
| Alice | 1 | 2025-05-20 |
| Bob | 2 | 2025-05-28 |
| Chen | 3 | 2025-05-30 |
| Deepa | 4 | 2025-04-15 |
Table drivers
| column | type | constraints |
|---|
| driver_id | INT | PRIMARY KEY |
| name | VARCHAR(50) | NOT NULL |
| car_year | INT | NOT NULL |
Sample data:
| car_year | driver_id | name |
|---|
| 2018 | 10 | Diego |
| 2021 | 11 | Elena |
| 2015 | 12 | Farid |
Table trips
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| rider_id | INT | NOT NULL |
| driver_id | INT | NOT NULL |
| request_time | TIMESTAMP | NOT NULL |
| pickup_time | TIMESTAMP | |
| dropoff_time | TIMESTAMP | |
| status | VARCHAR(32) | NOT NULL |
| city | VARCHAR(50) | NOT NULL |
| surge_multiplier | DECIMAL(3,1) | NOT NULL |
| distance_km | DECIMAL(5,1) | NOT NULL |
| fare_usd | DECIMAL(6,2) | NOT NULL |
Sample data:
| city | distance_km | driver_id | dropoff_time | fare_usd | pickup_time | request_time | rider_id | status | surge_multiplier | trip_id |
|---|
| SF | 8 | 10 | 2025-05-25 20:25:00 | 24.5 | 2025-05-25 20:05:00 | 2025-05-25 19:55:00 | 1 | completed | 1.8 | 1 |
| SF | 5 | 11 | 2025-05-26 14:30:00 | 12 | 2025-05-26 14:15:00 | 2025-05-26 14:10:00 | 2 | completed | 1 | 2 |
| SF | 0 | 11 | NULL | 0 | 2025-05-26 20:30:00 | 2025-05-26 20:05:00 | 2 | cancelled_by_driver | 2 | 3 |
| NYC | 0 | 12 | NULL | 0 | NULL | 2025-05-27 02:30:00 | 3 | cancelled_by_rider | 1 | 4 |
| NYC | 6.5 | 10 | 2025-05-30 20:45:00 | 18 | 2025-05-30 20:25:00 | 2025-05-30 20:15:00 | 3 | completed | 1.7 | 5 |
| SF | 7 | 10 | 2025-05-31 14:30:00 | 16 | 2025-05-31 14:10:00 | 2025-05-31 14:05:00 | 1 | completed | 1 | 6 |
| SF | 9 | 10 | 2025-05-31 20:40:00 | 22 | 2025-05-31 20:20:00 | 2025-05-31 20:05:00 | 4 | completed | 1.6 | 7 |
| SF | 0 | 11 | NULL | 0 | NULL | 2025-06-01 02:10:00 | 4 | driver_no_show | 1 | 8 |
Table payments
| column | type | constraints |
|---|
| trip_id | INT | PRIMARY KEY |
| amount_usd | DECIMAL(6,2) | NOT NULL |
| method | VARCHAR(20) | NOT NULL |
| card_hash | VARCHAR(100) | |
| charge_time | TIMESTAMP | NOT NULL |
| success | BOOLEAN | NOT NULL |
Sample data:
| amount_usd | card_hash | charge_time | method | success | trip_id |
|---|
| 24.5 | abc123 | 2025-05-25 20:26:00 | card | true | 1 |
| 12 | xyz777 | 2025-05-26 14:31:00 | card | true | 2 |
| 0 | xyz777 | 2025-05-26 20:31:00 | card | false | 3 |
| 0 | qwe555 | 2025-05-27 02:32:00 | card | false | 4 |
| 18 | abc123 | 2025-05-30 20:46:00 | card | true | 5 |
| 16 | abc123 | 2025-05-31 14:31:00 | card | true | 6 |
| 22 | NULL | 2025-05-31 20:41:00 | cash | false | 7 |
| 0 | xyz777 | 2025-06-01 02:12:00 | card | false | 8 |
Table devices
| column | type | constraints |
|---|
| user_type | VARCHAR(10) | NOT NULL |
| user_id | INT | NOT NULL |
| device_id | VARCHAR(50) | NOT NULL |
Sample data:
| device_id | user_id | user_type |
|---|
| devA | 1 | rider |
| devB | 2 | rider |
| devC | 3 | rider |
| devB | 4 | rider |
| devX | 10 | driver |
| devY | 11 | driver |
| devZ | 12 | driver |
Expected output
Each row represents a pair of riders (rider_id_a < rider_id_b) that share either a device_id or card_hash within the last 30 days, with the earliest time this shared identifier is observed.
| evidence_type | evidence_value | first_seen_time | rider_id_a | rider_id_b |
|---|
| card | abc123 | 2025-05-25 20:26:00 | 1 | 3 |
| card | xyz777 | 2025-05-26 14:31:00 | 2 | 4 |
| device | devB | 2025-05-26 14:10:00 | 2 | 4 |
Hints
- For devices, aggregate per rider and device to get the earliest request_time in the 30-day window, then self-join on device_id to form rider pairs.
- For cards, map payments to riders via trips, compute the earliest charge_time per rider and card_hash, self-join on card_hash to form pairs, and UNION these results with the device-based pairs.