Buyers Who Never Browsed

An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
easy
Seniority
L3
Asked in
Fidelity

Problem

Some 2026 purchases were made by buyers who never once appear in page_views, and the fraud team's review needs every one of those transactions. List each with the buyer's username and the transaction total, smallest amount first.

They bought without ever loading a page.

Tables

users

Columns: user_id INTEGER, username TEXT, email TEXT, signup_date TEXT, account_status TEXT, age_bucket TEXT

users: 10 of 200 rows
user_idusernameemailsignup_dateaccount_statusage_bucket
100alicealice@example.com2025-02-02inactive25-34
197aaron42aaron42@example.com2026-03-03suspended35-44
294 ameliaamelia@example.com2024-04-04pending_verification45-54
391arjun arjun@example.com2025-05-05active55-64
488ava99ava99@example.com2026-06-06inactive65+
585andrew_kandrew_k@example.com2024-07-07suspendedNULL
682anikaanika@example.com2025-08-08 16:34:00pending_verification18-24
779aidenaiden@example.com2026-09-09 14:53:00active25-34
876aria_baria_b@example.com2024-10-10inactive35-44
973brianbrian@example.com2025-11-11suspended45-54

transactions

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

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

page_views

Columns: view_id INTEGER, user_id INTEGER, viewed_at TEXT, page_url TEXT, referrer TEXT, dur_ms INTEGER, device TEXT

page_views: 10 of 200 rows
view_idpage_urluser_idreferrerdur_msdeviceviewed_at
5053/products1555amazon.com637desktop2026-09-02 01:09:00
5106/checkout1652twitter.com774tablet2026-09-03 02:18:00
5159/profile1749github.com911Mobile2026-09-04 03:27:00
5212/search1846NULL1048DESKTOP2026-09-05 04:36:00
5265/about1943reddit.com1185mobile2026-09-06 05:45:00
5318/blog/intro1555NULLNULLdesktop2026-09-07 06:54:00
5371/pricing1652bing.com1459tablet2026-09-08 07:03:00
5424/docs/api1749news.ycombinator.com1596Mobile2026-09-09 08:12:00
5477/settings1846NULL1733DESKTOP2026-09-10 09:21:00
5530/Home1943bing.com1870mobile2026-09-11 10:30:00

Expected output

First 5 of 20 rows
usernametotal_amount
arjun 50.4
aiden104.28
beatrice158.16
arjun 252.45
aiden306.33

Worked solution and explanation

What this is really asking

This is a set difference wearing a fraud-investigation costume: the year's purchases minus every buyer who shows up in page_views. Absence is the whole problem, and you cannot see absence by joining to the rows that exist. The candidate who inner-joins page_views returns precisely the browsers the review wanted gone, each one repeated about 62 times. The one who scopes page_views to the year through viewed_at quietly turns 'never browsed' into 'did not browse this year' and lets real browsers in. Any honest anti-join works; the trap is everything that is not one.


Break down the requirements

Step 1: Scope transactions to the target year

Filter the driving table to the target year: strftime('%Y', t.transaction_date) = '2026'. The year lives on transaction_date, so the page_views side stays completely unrestricted.

Step 2: Join to users for the username

Inner-join users on user_id to expose each buyer's username for the output. Every transaction references a real user, so this join neither adds nor drops rows.

Step 3: Exclude anyone with a page view

Apply the anti-join: the correlated NOT EXISTS keeps only buyers whose user_id never appears in page_views. One matching row is enough to drop the transaction, and nothing from page_views reaches the output.

Step 4: Order smallest purchase first

Sort by total_amount, smallest first. The only equal totals here are one buyer's repeat purchases at the same price, and those rows are identical, so a second sort key would change nothing.


Two anti-joins, one inverse

Inner join to `page_views`
JOIN page_views pv ON pv.user_id = t.user_id keeps only buyers who browsed: the inverse set, fanned out by every view. No WHERE clause downstream can recover the buyers it already dropped.
Anti-join
NOT EXISTS (or LEFT JOIN plus pv.user_id IS NULL) keeps the purchases with no partner. Both are correct; NOT EXISTS says it directly and stops at the first match.

The solution

NOT EXISTS anti-join
SELECT u.username, t.total_amount
FROM transactions t
JOIN users u ON t.user_id = u.user_id
WHERE strftime('%Y', t.transaction_date) = '2026'
  AND NOT EXISTS (
        SELECT 1 FROM page_views pv WHERE pv.user_id = t.user_id
      )
ORDER BY t.total_amount;

Common follow-up questions