Double Vision
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
- UKG
Problem
Ahead of a CRM migration, the data quality team is hunting for email addresses that were entered against more than one account. For each email tied to multiple accounts, return the address, how many accounts carry it, and the earliest and most recent signup dates, with the most-repeated addresses first.
Before the records move, the ones wearing the same name twice have to surface.
Tables
users
Columns: user_id INTEGER, email TEXT, signup_date TEXT, username TEXT, account_status TEXT, age_bucket TEXT
| user_id | username | signup_date | account_status | age_bucket | |
|---|---|---|---|---|---|
| 100 | alice | alice@example.com | 2025-02-02 | inactive | 25-34 |
| 197 | aaron42 | aaron42@example.com | 2026-03-03 | suspended | 35-44 |
| 294 | amelia | amelia@example.com | 2024-04-04 | pending_verification | 45-54 |
| 391 | arjun | arjun@example.com | 2025-05-05 | active | 55-64 |
| 488 | ava99 | ava99@example.com | 2026-06-06 | inactive | 65+ |
| 585 | andrew_k | andrew_k@example.com | 2024-07-07 | suspended | NULL |
| 682 | anika | anika@example.com | 2025-08-08 16:34:00 | pending_verification | 18-24 |
| 779 | aiden | aiden@example.com | 2026-09-09 14:53:00 | active | 25-34 |
| 876 | aria_b | aria_b@example.com | 2024-10-10 | inactive | 35-44 |
| 973 | brian | brian@example.com | 2025-11-11 | suspended | 45-54 |
Expected output
| occurrence_count | earliest_signup | latest_signup | |
|---|---|---|---|
| haruki@example.com | 2 | 2025-07-07 | 2026-09-02 |
| hayley_b@example.com | 2 | 2024-07-27 | 2026-02-12 |
| henry@example.com | 2 | 2023-05-25 | 2024-04-24 |
| hiro@example.com | 2 | 2024-12-02 | 2025-05-25 |
Worked solution and explanation
What this problem really is
Strip the migration costume off and this is a 'collapse rows into groups, then keep only the crowded groups' question. Everyone writes GROUP BY email. Two things separate the clean answer from the messy one. First, the count filter has to run AFTER the rows are grouped, which means HAVING, not WHERE. Second, a pile of NULL-email rows is a missing-data defect, not a duplicate. Forget to exclude NULL and you hand the migration team a phantom duplicate that is really every account someone never gave an email: 16 accounts on this data, sitting at the top of the list.
Break down the requirements
Step 1: Group by email
The team wants addresses tied to more than one account, so the unit of the answer is the address. NULL is the absence of an address, not one, so a clump of NULL-email rows is a missing-data problem, not a duplicate-email one. Collapse the remaining rows by address with GROUP BY email.
Step 2: Filter to real duplicates
Drop missing addresses up front with WHERE email IS NOT NULL, then keep only the crowded groups with HAVING COUNT(*) > 1. The count condition has to be HAVING: WHERE runs before grouping, so it cannot see a per-group count.
Step 3: Extract the date boundaries and order
Inside each surviving group, COUNT(*) is the occurrence count, MIN(signup_date) is the earliest signup and MAX(signup_date) is the most recent. Order by occurrence_count DESC, then email ASC, so the worst offenders surface first and ties come back in a stable order.
The solution
SELECT email, COUNT(*) AS occurrence_count, MIN(signup_date) AS earliest_signup, MAX(signup_date) AS latest_signup FROM users WHERE email IS NOT NULL GROUP BY email HAVING COUNT(*) > 1 ORDER BY occurrence_count DESC, email ASCCommon follow-up questions
- How would you handle case-insensitive duplicate detection? (Tests `LOWER(email)` in the grouping key and awareness of collation settings.)
- What if you also needed to return the `user_id` values of the duplicate accounts? (Tests `GROUP_CONCAT` / `STRING_AGG`, or a join back to `users`, to list the ids in each duplicate group.)
- How would you deduplicate and keep only the earliest account? (Tests `DELETE` with a subquery, or `ROW_NUMBER()` partitioned by `email` and ordered by `signup_date`.)