Same-Day Session and Transaction Correlation
A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- hard
- Seniority
- L4
- Asked in
- Walmart
Problem
Show how many separate days each customer visited the site, how many purchases they made, and their average purchase value. The growth team's browse-and-buy profile covers only customers who appear in both user_sessions and transactions.
Same day session and purchase. Connected?
Tables
user_sessions
Columns: session_id INTEGER, user_id INTEGER, device_id INTEGER, session_start TEXT, session_duration_sec INTEGER, pages_viewed INTEGER
| session_id | user_id | device_id | session_start | session_duration_sec | pages_viewed |
|---|---|---|---|---|---|
| 7061 | 197 | 230 | 2026-02-02 01:00:00 | 53 | 3 |
| 7122 | 294 | 259 | 2026-03-03 02:00:00 | 76 | 6 |
| 7183 | 391 | 288 | 2026-04-04 03:00:00 | 99 | 9 |
| 7244 | 488 | 317 | 2026-05-05 04:00:00 | 122 | 12 |
| 7305 | 585 | 346 | 2026-06-06 05:00:00 | 145 | 15 |
| 7366 | 682 | 375 | 2026-07-07 06:00:00 | 168 | 18 |
| 7427 | 779 | 404 | 2026-08-08 07:00:00 | 191 | 21 |
| 7488 | 876 | NULL | 2026-09-09 08:00:00 | 214 | 24 |
| 7549 | 973 | 462 | 2026-01-10 09:00:00 | 237 | 27 |
| 7610 | 1070 | 491 | 2026-02-11 10:00:00 | 260 | 30 |
transactions
Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT
| transaction_id | user_id | product_id | quantity | total_amount | transaction_date |
|---|---|---|---|---|---|
| 1067 | 197 | 1001 | 2 | 23.46 | 2026-02-02 |
| 1134 | 294 | 1050 | 3 | 36.93 | 2026-03-03 |
| 1201 | 391 | 1099 | 4 | 50.4 | 2026-04-04 |
| 1268 | 488 | 1148 | 5 | 63.87 | 2026-05-05 |
| 1335 | 585 | 1197 | 0 | 0 | 2026-06-06 |
| 1402 | 682 | 1246 | 2 | 90.81 | 2026-07-07 |
| 1469 | 779 | 1295 | 3 | 104.28 | 2026-08-08 |
| 1536 | 876 | 1344 | 4 | 117.75 | 2026-09-09 |
| 1603 | 973 | 1393 | 5 | -45 | 2026-01-10 |
| 1670 | 1070 | 1442 | 1 | 144.69 | 2026-02-11 |
Expected output
| user_id | active_days | total_transactions | avg_purchase_value |
|---|---|---|---|
| 100 | 6 | 12 | 717.165 |
| 197 | 5 | 14 | 629.61 |
| 294 | 5 | 14 | 643.0799999999999 |
| 391 | 5 | 14 | 656.55 |
| 488 | 5 | 14 | 670.02 |
Worked solution and explanation
What this really is
Strip the label and this is the intersection of two per-user event streams: the people who show up in both the browsing log and the buying log. The INNER JOIN is the load-bearing move, and the moment you join user_sessions to transactions on user_id, every purchase repeats once for each session that user opened. A plain COUNT(t.transaction_id) then counts session-purchase pairs, not purchases; the query still runs clean, so the wrong numbers ship quietly.
Building it
Step 1: Inner join to keep only browsers who bought
INNER JOIN user_sessions to transactions on user_id keeps only the users present in both streams; browse-only users and buy-only users drop out. That is the 'both browse and buy' requirement expressed as a join.
Step 2: Fold same-day sessions into calendar days
session_start is a timestamp carrying a clock time, so several sessions in one day are several rows. Wrap date() and count distinct: COUNT(DISTINCT date(s.session_start)). Without date() you count instants; without DISTINCT you count the fanned rows. User 100 opened 8 sessions across 6 days and must report 6.
Step 3: Count purchases duplicate-safe, average the money
The join fans each purchase out once per session, so use COUNT(DISTINCT t.transaction_id). For spend, AVG(t.total_amount) is safe because every purchase repeats the same number of times. Leave it unrounded: several users average exactly half a cent (user 100 lands on 717.165), and rounding that to cents flips on floating-point noise. A SUM would be multiplied by the session count.
- Naive: count after the join
COUNT(t.transaction_id)over the joined rows is multiplied by the sessions per user, so user 100 reports 96 purchases instead of 12.- Correct: distinct counts and an average
COUNT(DISTINCT t.transaction_id)andCOUNT(DISTINCT date(s.session_start))recover the true counts, andAVG(t.total_amount)is invariant under a uniform fan-out.
The solution
SELECT s.user_id,
COUNT(DISTINCT date(s.session_start)) AS active_days,
COUNT(DISTINCT t.transaction_id) AS total_transactions,
AVG(t.total_amount) AS avg_purchase_value
FROM user_sessions s
INNER JOIN transactions t ON s.user_id = t.user_id
GROUP BY s.user_idOne flat grouped join; date() folds same-day sessions, DISTINCT undoes the fan-out, and AVG is fan-out-invariant.
Common follow-up questions
- A user has 5 sessions across 2 days and 3 purchases. After the join, how many rows do they contribute, and what are `active_days`, `total_transactions` and `avg_purchase_value`? (Tests whether they understand the fan-out arithmetic, not just having memorized 'add DISTINCT'.)
- How would you also report each user's true total spend, not just the average? (Forces the realization that a correct `SUM` needs `transactions` aggregated before the join.)