Introduction
When working with relational databases, you often encounter duplicate rows that can skew analysis, inflate counts, and waste storage. Because of that, sQL provides several built‑in methods to eliminate these duplicates directly in your queries, ensuring clean, reliable data. Because of that, this article explores the most common techniques for SQL removing duplicates, including SELECT DISTINCT, GROUP BY, window functions like ROW_NUMBER(), and CTEs, and explains when each approach is best suited. By the end, you’ll have a clear roadmap for handling duplicate data in SELECT statements, updates, and bulk operations Most people skip this — try not to..
Techniques Overview
Below are the primary strategies you can apply to remove duplicates in a SQL query:
- SELECT DISTINCT – Returns only unique combinations of the selected columns.
- GROUP BY with aggregation – Groups rows by a key column and aggregates other columns (e.g., MIN, MAX, SUM).
- Window functions (ROW_NUMBER, RANK, DENSE_RANK) – Assigns a sequential number to each row within a partition, allowing you to filter out all but the first occurrence.
- Common Table Expressions (CTEs) + window functions – Enables reusable logic for complex duplicate‑removal scenarios.
- UNION / UNION ALL – Merges two result sets while automatically eliminating duplicates.
- DELETE with a self‑join or subquery – Physically removes duplicate rows from a table.
Each method has its own strengths, and choosing the right one depends on whether you need a read‑only view of deduplicated data or want to modify the underlying table Most people skip this — try not to..
Using SELECT DISTINCT
How it works
SELECT DISTINCT instructs the database engine to return only unique rows based on the columns listed after SELECT. It operates at the query level, meaning the original table remains untouched.
SELECT DISTINCT customer_id, order_date, amount
FROM orders
ORDER BY order_date;
When to use it
- When you need a snapshot of unique records for reporting.
- When the duplicate columns are exact copies (same values in all selected fields).
- When you do not need to preserve any of the duplicate rows for later analysis.
Limitations
- DISTINCT can be resource‑intensive on large datasets because it requires sorting or hashing all rows.
- It does not give you control over which duplicate to keep; all duplicates are collapsed into a single row.
Removing Duplicates with GROUP BY
How it works
GROUP BY aggregates rows that share the same values in specified columns. By pairing it with aggregate functions, you can keep one representative value for each group Most people skip this — try not to..
SELECT customer_id,
MIN(order_date) AS first_order_date,
SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id;
When to use it
- When you need to summarize duplicate data (e.g., total sales per customer).
- When you want to keep the minimum, maximum, average, or other statistical value from the duplicates.
Variations
- COUNT(*) can reveal how many duplicates existed per group.
- MAX(order_id) can be used to keep the “latest” duplicate if you have a surrogate key.
Window Functions for Precise Deduplication
ROW_NUMBER() approach
Window functions allow you to label each duplicate with a sequential number, then filter out all but the desired rows.
WITH ranked_orders AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id, order_date, amount
ORDER BY order_id ASC
) AS rn
FROM orders
)
SELECT customer_id, order_date, amount
FROM ranked_orders
WHERE rn = 1;
When to use it
- When you need to choose which duplicate to keep (e.g., the earliest, the latest, or based on a priority column).
- When you have multiple columns that together define a duplicate set but you want to keep one row per logical key.
Other window functions
- RANK() and DENSE_RANK() are useful when duplicates share the same ranking value (e.g., ties).
- LAG() and LEAD() can compare a row with its neighbor to detect and filter duplicates programmatically.
CTEs for Complex Scenarios
Combining CTEs with multiple deduplication steps
For more involved situations—such as removing duplicates across three or more columns while preserving additional metadata—you can nest CTEs.
WITH first_pass AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY col_a, col_b
ORDER BY col_c DESC
) AS rn1
FROM source_table
),
second_pass AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY col_a, col_b, col_d
ORDER BY col_e ASC
) AS rn2
FROM first_pass
WHERE rn1 = 1
)
SELECT *
FROM second_pass
WHERE rn2 = 1;
Benefits
- Readability: Each step is isolated, making the logic easier to audit.
- Reusability: The CTE can be referenced multiple times within the same session.
- Performance: Some databases can materialize intermediate CTE results, which may improve execution plans.
UNION and UNION ALL for Set Operations
How they differ
- UNION removes duplicate rows automatically because it returns only distinct rows from the combined result sets.
- UNION ALL retains all rows, including duplicates, and is faster because it skips the duplicate‑elimination step.
Example
SELECT customer_id, order_date, amount
FROM orders_q1
UNION
SELECT customer_id, order_date, amount
FROM orders_q2;
If you need a cleaned combined view without duplicates, use UNION. If you want to keep every row (including duplicates) for a different purpose, use UNION ALL.
Physically Removing Duplicate Rows
DELETE with a self‑join
When you want to **purge