Every Touch Counts

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

Domain
SQL
Difficulty
medium
Seniority
junior
Asked in
Copart

Problem

Find each shopper's 2026 touchpoint total on the vehicle auction marketplace, most active first, where every row in ad_impressions and every row in search_queries from that year counts as one touchpoint. Touchpoints logged without a user_id still count, reported together on a single row of their own.

A bid, a click, a search: each one is a vote. Total them honestly.

Tables

ad_impressions

Columns: impression_id INTEGER, user_id INTEGER, ad_campaign TEXT, impression_time TEXT, clicked INTEGER, revenue REAL

ad_impressions: 10 of 200 rows
impression_iduser_idad_campaignimpression_timeclickedrevenue
253100BRAND_AWARENESS_Q12026-02-02 01:07:000NULL
306197PRODUCT_LAUNCH_X2026-03-03 02:14:000NULL
359294RETARGETING_CART2026-04-04 03:21:000NULL
412391HOLIDAY_PROMO2026-05-05 04:28:000NULL
465488NEW_USER_ACQU2026-06-06 05:35:0010.2
518NULLLOYALTY_PROGRAM2026-07-07 06:42:000NULL
571682FLASH_SALE_48H2026-08-08 07:49:000NULL
624779SUMMER_SALE_20242026-09-09 08:56:000NULL
677876BRAND_AWARENESS_Q12026-01-10 09:03:000NULL
730973PRODUCT_LAUNCH_X2026-02-11 10:10:0010.35

search_queries

Columns: query_id INTEGER, user_id INTEGER, search_term TEXT, results_count INTEGER, clicked_result INTEGER, query_time TEXT

search_queries: 10 of 200 rows
query_iduser_idsearch_termresults_countclicked_resultquery_time
2071100laptop deals1722026-02-02 01:11:00
2142197phone case iphone 153432026-03-03 02:22:00
2213294usb-c cable 6ft51NULL2026-04-04 03:33:00
2284NULLgaming mouse6852026-05-05 04:44:00
2355488mechanical keyboard8562026-06-06 05:55:00
24265854k monitor102NULL2026-07-07 06:06:00
2497682standing desk11982026-08-08 07:17:00
2568NULLwebcam 1080p13692026-09-09 08:28:00
2639876bluetooth speaker153NULL2026-01-10 09:39:00
2710973smart watch17012026-02-11 10:50:00

Expected output

First 5 of 82 rows
user_idtouchpoint_count
NULL46
48815
97311
87611
68211

Worked solution and explanation

Why this problem exists in real interviews

This is the UNION vs UNION ALL question wearing a marketing hat. The real skill being probed: do you know that UNION quietly deduplicates rows while UNION ALL keeps every one? You are combining two activity streams and counting events per user. If you reach for UNION, two ad impressions from the same shopper collapse into a single row before the count ever runs, so a power user with 40 touchpoints reports the same number as someone who showed up once. Your 'most engaged' ranking becomes noise, and nothing in the output looks obviously wrong.


Break down the requirements

Step 1: Reduce each source to one row per event

From ad_impressions and from search_queries, select just user_id, each filtered to 2026 on its own time column. One row out per impression and per search is exactly what 'one touchpoint' means. Do not filter out a missing user_id: the statement wants those touchpoints kept as their own row.

Step 2: Stack the streams, keep every row

UNION ALL the two selects. This is the whole problem: UNION ALL preserves duplicates so the row count equals the event count. UNION would fold identical user_id rows together and destroy the metric before you can measure it.

Step 3: Count and order

Wrap the stacked stream in a subquery, GROUP BY user_id, COUNT(*) as touchpoint_count, then sort by touchpoint_count descending. GROUP BY puts every NULL user_id into one group, which is exactly the anonymous row the statement asks for.


The solution

TOUCHPOINTS PER USER
SELECT user_id,
       COUNT(*) AS touchpoint_count
FROM (
    SELECT user_id FROM ad_impressions
    WHERE strftime('%Y', impression_time) = '2026'
    UNION ALL
    SELECT user_id FROM search_queries
    WHERE strftime('%Y', query_time) = '2026'
) activity
GROUP BY user_id
ORDER BY touchpoint_count DESC
UNION ALL (correct here)
Appends both streams row for row. A user with 12 impressions and 8 searches contributes 20 rows, so COUNT(*) returns 20. No hidden work, no sort to dedup.
UNION (wrong here)
Runs an implicit DISTINCT across the whole stacked set. Selecting only user_id means each user collapses to one row, so every COUNT(*) returns 1 and the ranking is meaningless. It also pays for a sort or hash you never wanted.

Common follow-up questions