Advanced SQL & Data Engineering • Training 08
Article-Training • SQL Server–Friendly Series

Dedupe with ROW_NUMBER

Do Not Just Find Duplicates — Decide Which Row Survives

ROW_NUMBER turns duplicate detection into a controlled survivor decision.

Duplicate Key → Partition Rows → Rank by Business Rule → Keep rn = 1 → Review / Remove rn > 1
Same Email
→
ROW_NUMBER
→
Winner = 1
Others
→
Review
→
Archive / Delete
8learning modules
24interactive practices
5rapid review questions
50%certificate threshold
Learning target
Use ROW_NUMBER to define which duplicate row survives — safely and deterministically.
Practice progress0 / 24

Complete 12 of 24 practices (50%) and enter your name to unlock the Certificate of Participation.

MODULE 01
🧠

Duplicate Detection vs Survivor Selection

Finding duplicates and deciding which row survives are different problems. GROUP BY/HAVING identifies duplicate business keys. ROW_NUMBER assigns an ordered position to every row within each key group.

🧹
Detection says 'we have three'; dedupe says 'keep this one'

Three customer rows may share the same email. The business rule might be to keep the newest verified record and classify the other two as non-survivors.

Core ideas

  • PARTITION BY defines the duplicate group
  • ORDER BY defines survivor priority
  • rn = 1 usually means survivor
  • rn > 1 identifies rows for review, archive or removal
Core dedupe pattern
WITH Ranked AS
(
    SELECT CustomerID,
           Email,
           ModifiedDate,
           ROW_NUMBER() OVER
           (
               PARTITION BY Email
               ORDER BY ModifiedDate DESC, CustomerID DESC
           ) AS rn
    FROM dbo.Customer
)
SELECT *
FROM Ranked
ORDER BY Email, rn;
✅

ROW_NUMBER is the bridge from 'duplicate exists' to 'this row wins for a documented reason.'

Practice — 3 cases

Practice 1 / Práctica 1
What does ROW_NUMBER add beyond GROUP BY/HAVING duplicate detection?
Practice 2 / Práctica 2
Which clause defines the duplicate group in ROW_NUMBER?
Practice 3 / Práctica 3
Which clause decides who becomes row number 1 inside each duplicate group?
MODULE 02
🧬

Define the Duplicate Key Before You Rank

The hardest part of deduplication is rarely the ROW_NUMBER syntax. It is agreeing on what 'duplicate' actually means. A technical duplicate key must reflect the business identity rule.

🧹
Wrong duplicate key = correct SQL deleting valid data

Two employees can share a last name. Two family members can share a phone number. A dedupe key must distinguish legitimate repetition from true duplication.

Core ideas

  • Use business identity, not convenience
  • Composite duplicate keys are often safer
  • Normalize values consistently before comparing
  • Review NULL semantics in duplicate keys
Composite duplicate key
ROW_NUMBER() OVER
(
    PARTITION BY
        EmployeeNumber,
        EffectiveDate,
        SourceSystem
    ORDER BY
        LoadTimestamp DESC,
        RowID DESC
) AS rn
✅

The PARTITION BY clause is a business rule disguised as SQL syntax.

Practice — 3 cases

Practice 4 / Práctica 4
What should PARTITION BY contain?
Practice 5 / Práctica 5
Why is deduping by Email alone sometimes dangerous?
Practice 6 / Práctica 6
Which is the strongest dedupe design step before writing DELETE?
MODULE 03
🏆

Write the Survivor Rule Explicitly

ORDER BY inside ROW_NUMBER is where the business decides what 'best row' means. Newest is only one possible rule; verified, complete, trusted-source or manually approved records may deserve priority.

🧹
The first row should win for a reason, not by accident

If two records have the same ModifiedDate, SQL Server still needs a stable tie-breaker. Add a unique key last so the ranking is deterministic.

Core ideas

  • Put highest-priority business rule first
  • Add deterministic tie-breakers
  • Avoid random or unstable ordering
  • Document why rn = 1 is the survivor
Priority-based survivor
ROW_NUMBER() OVER
(
    PARTITION BY Email
    ORDER BY
        IsVerified DESC,
        DataQualityScore DESC,
        ModifiedDate DESC,
        CustomerID DESC
) AS rn
✅

Deterministic ranking turns deduplication from a guess into an auditable rule.

Practice — 3 cases

Practice 7 / Práctica 7
Why must the ROW_NUMBER ORDER BY be deterministic?
Practice 8 / Práctica 8
What is a good tie-breaker after ModifiedDate DESC?
Practice 9 / Práctica 9
What does ORDER BY IsVerified DESC, ModifiedDate DESC express?
MODULE 04
👀

Preview First: Winners, Losers and Duplicate Groups

A safe dedupe process is inspection-first. Build the ranking, display the full duplicate groups, verify rn = 1, then decide what to do with rn > 1.

🧹
SELECT before DELETE

A dedupe query that returns exactly the rows you intended to remove is evidence. A DELETE written first is risk.

Core ideas

  • Preview the complete duplicate group
  • Label Survivor vs Duplicate in output
  • Count duplicate groups and removable rows
  • Spot-check edge cases before any destructive action
Preview classification
WITH Ranked AS
(
    SELECT *,
           ROW_NUMBER() OVER
           (
               PARTITION BY Email
               ORDER BY ModifiedDate DESC, CustomerID DESC
           ) AS rn
    FROM dbo.Customer
)
SELECT *,
       CASE WHEN rn = 1
            THEN 'SURVIVOR'
            ELSE 'DUPLICATE'
       END AS DedupeStatus
FROM Ranked
WHERE Email IN
(
    SELECT Email
    FROM dbo.Customer
    GROUP BY Email
    HAVING COUNT(*) > 1
)
ORDER BY Email, rn;
✅

A preview is not extra work; it is the evidence that makes destructive dedupe defensible.

Practice — 3 cases

Practice 10 / Práctica 10
What should you do before deleting rn > 1 rows?
Practice 11 / Práctica 11
Which review query focuses only on duplicate groups?
Practice 12 / Práctica 12
Why is previewing both winners and losers useful?
MODULE 05
🗑️

Delete Safely with a Ranked CTE

Once the ranking has been validated, SQL Server can delete non-survivors through an updatable CTE. The syntax is short; the safety process around it matters more than the syntax.

🧹
Short DELETE, long checklist

The destructive statement may be only one line — DELETE FROM Ranked WHERE rn > 1 — but production discipline should include counts, transaction control, archive options and post-delete verification.

Core ideas

  • Delete only after preview validation
  • Use explicit transactions for controlled runs
  • Capture affected rows when auditability matters
  • Verify unique business keys after the operation
Controlled delete pattern
BEGIN TRANSACTION;

WITH Ranked AS
(
    SELECT CustomerID,
           ROW_NUMBER() OVER
           (
               PARTITION BY Email
               ORDER BY ModifiedDate DESC, CustomerID DESC
           ) AS rn
    FROM dbo.Customer
)
DELETE FROM Ranked
WHERE rn > 1;

-- Validate counts and survivors here.

-- COMMIT TRANSACTION;
-- or ROLLBACK TRANSACTION;
✅

The DELETE should be the final step of a validated dedupe process, not the first experiment.

Practice — 3 cases

Practice 13 / Práctica 13
Can SQL Server delete directly from a CTE that contains ROW_NUMBER?
Practice 14 / Práctica 14
What is the usual delete condition after ranking?
Practice 15 / Práctica 15
What is the safest production practice around destructive dedupe?
MODULE 06
📦

Archive Before Delete: Keep an Audit Trail

Not every duplicate should disappear without trace. In regulated, operational or high-value systems, archive the non-survivors with enough metadata to reconstruct what happened.

🧹
Dedupe is a data lineage event

If someone asks six months later why CustomerID 8124 disappeared, an archive row with batch ID and survivor reference turns a mystery into an answer.

Core ideas

  • Preserve removed rows when business risk warrants it
  • Capture batch/rule metadata
  • Use OUTPUT DELETED for controlled capture
  • Define archive retention separately from operational table cleanup
Capture deleted rows
DELETE d
OUTPUT
    DELETED.CustomerID,
    DELETED.Email,
    DELETED.ModifiedDate,
    @BatchID,
    SYSUTCDATETIME()
INTO dbo.Customer_DedupeArchive
(
    CustomerID,
    Email,
    ModifiedDate,
    DedupeBatchID,
    ArchivedAtUTC
)
FROM dbo.Customer AS d
JOIN #RowsToDelete AS x
  ON x.CustomerID = d.CustomerID;
✅

An archive path converts destructive cleanup into an auditable data-quality operation.

Practice — 3 cases

Practice 16 / Práctica 16
When might you archive duplicate rows instead of deleting them immediately?
Practice 17 / Práctica 17
What can the OUTPUT clause help capture during DELETE?
Practice 18 / Práctica 18
What metadata is useful in a dedupe archive table?
MODULE 07
📈

Performance: Sorting Millions of Duplicates

ROW_NUMBER often needs rows grouped and ordered by the PARTITION BY and ORDER BY keys. On large tables that can mean significant scans, sorts, memory grants and tempdb work.

🧹
A good survivor rule can still need a good access path

If the table has 200 million rows but only yesterday's load can contain new duplicates, filtering the candidate set before ranking can matter more than micro-optimizing syntax.

Core ideas

  • Filter candidate rows when the business process allows it
  • Index duplicate keys and ordering columns when justified
  • Watch Sort, memory grants and spills
  • Measure write cost before adding wide dedupe indexes
Supporting index idea
CREATE INDEX IX_Customer_Dedupe
ON dbo.Customer
(
    Email,
    IsVerified DESC,
    ModifiedDate DESC,
    CustomerID DESC
);

-- Validate with:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
-- + Actual Execution Plan
✅

The best dedupe performance often comes from reducing the rows that need ranking and giving SQL Server useful ordering.

Practice — 3 cases

Practice 19 / Práctica 19
What index shape can often help a ROW_NUMBER dedupe query?
Practice 20 / Práctica 20
Why can ROW_NUMBER dedupe become expensive on a large table?
Practice 21 / Práctica 21
What should you measure before and after an index added for dedupe?
MODULE 08
🏭

Production Dedupe: Clean Once, Prevent the Next Duplicate

A dedupe script fixes existing data. A production data-quality solution also addresses why duplicates were created: missing uniqueness rules, retry logic, ETL replay, source-system duplication or weak matching rules.

🧹
Cleanup without prevention is a recurring job, not a solution

If the same duplicates return tomorrow, ROW_NUMBER did its job but the pipeline did not. Add prevention where the business identity can safely support it.

Core ideas

  • Identify the upstream duplicate source
  • Use unique constraints when the business rule is truly unique
  • Make ETL loads idempotent where possible
  • Monitor duplicate counts as a data-quality KPI
Production checklist
1. Define the business duplicate key
2. Define the survivor priority
3. Add deterministic tie-breakers
4. Preview full duplicate groups
5. Count survivors and non-survivors
6. Archive / transaction-protect destructive changes
7. Delete only validated rn > 1 rows
8. Recheck duplicate counts after cleanup
9. Fix the upstream cause
10. Add constraints / monitoring where appropriate
✅

ROW_NUMBER solves the ranking problem; durable data quality also requires prevention, controls and monitoring.

Practice — 3 cases

Practice 22 / Práctica 22
What should happen after a dedupe cleanup to prevent recurrence?
Practice 23 / Práctica 23
When is a UNIQUE constraint or index appropriate after dedupe?
Practice 24 / Práctica 24
What is the strongest production rule for ROW_NUMBER dedupe?
5-Question Knowledge Check

Can you decide which duplicate row should survive?

Open each item after answering it in your own words. The 24 interactive practices above drive certificate progress.

1. What is the difference between duplicate detection and ROW_NUMBER dedupe?

Detection identifies duplicate groups; ROW_NUMBER ranks rows so a survivor can be chosen.

2. What does PARTITION BY represent in dedupe logic?

The business key combination that defines the duplicate group.

3. Why do deterministic tie-breakers matter?

They make survivor selection stable and repeatable.

4. Why should you preview before DELETE?

To verify the survivor rule before making destructive changes.

5. What makes dedupe production-ready?

Explicit rules, deterministic ranking, validation, auditability and recurrence prevention.

ROW_NUMBER Dedupe Blueprint

A reusable survivor-selection pattern

LayerPurpose
PARTITION BYDefine the duplicate business key
ORDER BYExpress survivor priority and tie-breakers
rn = 1Chosen survivor
rn > 1Review / archive / remove candidates
PreventionFix upstream cause and add controls

Certificate of Participation

Complete at least 12 of the 24 practice cases (50%) and enter your name.

0 / 24 • 0%
Production note

ROW_NUMBER dedupe is a survivor-selection process, not just a duplicate-finding trick.