SQL Interview Questions · Question 20

Write a query to delete duplicate records while keeping the newest record for each email?

Interview preparation resource from Gate Smashers.

Interview-ready answer

Rank each email group by created_at descending and delete rows whose row number is greater than one. Use ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC, customer_id DESC) to mark the newest row (rn = 1) per email and remove rows with rn > 1. Test the DELETE inside a transaction and preview the ranked rows before deleting.

Customer_Records
customer_idcustomer_nameemailcreated_at
301Nikhil Jainnikhil@email.com2024-01-12 10:15
302Sara Alisara@email.com2024-02-03 09:10
303Nikhil Jainnikhil@email.com2024-05-21 18:30
304Dev Pateldev@email.com2024-06-11 12:45
SQL querysql
WITH ranked AS (
  SELECT
    customer_id,
    ROW_NUMBER() OVER (
      PARTITION BY email
      ORDER BY created_at DESC, customer_id DESC
    ) AS rn
  FROM Customer_Records
)
DELETE FROM Customer_Records
WHERE customer_id IN (
  SELECT customer_id FROM ranked WHERE rn > 1
);
Understand it clearly

Solution (one-liner)

Rank each email group by created_at descending and delete rows whose row number is greater than one. The ROW_NUMBER() window function identifies the newest row per email; then delete all rows with rn > 1.

  • Core idea: Use ROW_NUMBER() PARTITION BY email ORDER BY created_at DESC (plus a tie-breaker) and delete where rn > 1.

How it works

ROW_NUMBER assigns rn = 1 to the newest row in each email partition; all older duplicates receive rn > 1. The query builds a ranked CTE and then deletes rows that are not the top-ranked record for their email.

  • Partitioning: PARTITION BY email groups rows by email address.
  • Ordering: ORDER BY created_at DESC picks the newest row first.
  • Selection: Rows with rn > 1 are older duplicates to be deleted.

Tie-breaker & determinism

When timestamps can match, add a deterministic tie-breaker so the engine always picks the same row to keep. In the supplied query customer_id DESC is used as the tie-breaker so the row with the highest customer_id among equal timestamps is kept.

  • Tie-breaker: Include an additional ORDER BY column (e.g., customer_id DESC) to ensure deterministic behavior when created_at values are identical.

Safety & testing

DELETE-with-CTE syntax and behavior can vary across databases. Always preview the ranked results and run the actual DELETE inside a transaction you can roll back if needed.

  • Preview: Run the SELECT from the ranked CTE to confirm rn assignments before deleting.
  • Transaction: Wrap the DELETE in a transaction so you can roll back if the deletion is not as expected.