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

user_sessions: 10 of 200 rows
session_iduser_iddevice_idsession_startsession_duration_secpages_viewed
70611972302026-02-02 01:00:00533
71222942592026-03-03 02:00:00766
71833912882026-04-04 03:00:00999
72444883172026-05-05 04:00:0012212
73055853462026-06-06 05:00:0014515
73666823752026-07-07 06:00:0016818
74277794042026-08-08 07:00:0019121
7488876NULL2026-09-09 08:00:0021424
75499734622026-01-10 09:00:0023727
761010704912026-02-11 10:00:0026030

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 15 rows
user_idactive_daystotal_transactionsavg_purchase_value
100612717.165
197514629.61
294514643.0799999999999
391514656.55
488514670.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) and COUNT(DISTINCT date(s.session_start)) recover the true counts, and AVG(t.total_amount) is invariant under a uniform fan-out.

The solution

The regulars: distinct days, purchases, and average value
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_id

One flat grouped join; date() folds same-day sessions, DISTINCT undoes the fan-out, and AVG is fan-out-invariant.

Common follow-up questions