Fundamentals

SELECT DISTINCT in SQL: Removing Duplicate Rows from Results

7 min readUpdated September 10, 2026Every example verified
On this page 10 sections ▾
  1. One column: the easy case
  2. Two columns: the case that trips everyone
  3. Counting distinct values
  4. DISTINCT and NULL
  5. DISTINCT with ORDER BY
  6. DISTINCT or GROUP BY?
  7. Performance, briefly
  8. Practice this topic
  9. Frequently asked questions
  10. Related reading

DISTINCT removes duplicate rows from a query result. That sentence contains the one thing people get wrong about it: it works on rows, not on columns. Once that clicks, the surprising results stop being surprising.

This page covers the single-column case, the multi-column case that catches everyone, counting distinct values, and the two situations where you should reach for GROUP BY instead. Every example runs against the sample database.

One column: the easy case

The products table has seven rows but fewer categories, because several products share one:

SELECT category FROM Products;
category
Hardware
Accessories
Hardware
Accessories
Software
Software
Services

7 rows · produced by running this query on the sample database

Add DISTINCT immediately after SELECT and each category appears once:

SELECT DISTINCT category FROM Products;
category
Hardware
Accessories
Software
Services

4 rows · produced by running this query on the sample database

Note where it goes. DISTINCT is not a function you wrap around a column — there is no DISTINCT(category) in SQL, even though it is often written that way in blog posts. It is a keyword that modifies the whole SELECT list, which is exactly why the next section behaves as it does.

Two columns: the case that trips everyone

Here is the mistake, in the form it usually takes. Someone wants one row per country and writes:

SELECT DISTINCT country, city FROM Customers;
countrycity
United KingdomLondon
United StatesAustin
JapanOsaka
TanzaniaArusha
PeruLima
PeruArequipa
LebanonBeirut
NorwayBergen

8 rows · produced by running this query on the sample database

Every row comes back. Not because DISTINCT failed, but because it did precisely what it says: it removed duplicate rows, and each country/city pair is unique even though the countries are not. Compare it with the single-column version:

SELECT DISTINCT country FROM Customers;
country
United Kingdom
United States
Japan
Tanzania
Peru
Lebanon
Norway

7 rows · produced by running this query on the sample database

Fewer rows, because now the row is the country. The rule to carry away: DISTINCT de-duplicates the combination of everything in the SELECT list. Adding a column can only ever increase the number of rows it returns, never decrease it.

There is no way to make DISTINCT apply to one column of several

If you need one row per country but also want to see a city, DISTINCT is the wrong tool — the question is really “which city?”, and SQL cannot guess. Group by the country and choose explicitly with an aggregate such as MIN(city), or number the rows with a window function and keep the first. Both are shown below.

Counting distinct values

COUNT(DISTINCT column) counts how many different values exist, which is a different question from how many rows exist:

SELECT COUNT(*)                 AS customers,
       COUNT(DISTINCT country)  AS countries
FROM Customers;
customerscountries
87

1 row · produced by running this query on the sample database

This is one of the most useful things in reporting SQL. When a table has one row per event, COUNT(*) counts events and COUNT(DISTINCT customer_id) counts the people behind them — confusing the two is how “active users” numbers get overstated:

SELECT COUNT(*)                      AS orders,
       COUNT(DISTINCT customer_id)   AS customers_who_ordered
FROM Orders;
orderscustomers_who_ordered
158

1 row · produced by running this query on the sample database

The aggregate functions guide covers the rest of the family, including why COUNT(*) and COUNT(column) disagree when NULLs are involved.

DISTINCT and NULL

For sorting and comparison, NULL is famously not equal to itself — but DISTINCT treats all NULLs as one value, so a column full of missing data yields a single NULL row:

SELECT DISTINCT manager_id FROM Employees;
manager_id
1
4
7
9
11
NULL

6 rows · produced by running this query on the sample database

One of those rows is NULL, standing in for every employee with no manager. That is the standard behaviour and it is what you almost always want, but it is worth knowing because it is inconsistent with =: two NULLs are “the same” to DISTINCT and “unknown” to a comparison. See IS NULL for the comparison side of that story.

DISTINCT with ORDER BY

Sorting works as usual, with one restriction:

SELECT DISTINCT country
FROM Customers
ORDER BY country;
country
Japan
Lebanon
Norway
Peru
Tanzania
United Kingdom
United States

7 rows · produced by running this query on the sample database

You can only sort by something that survives the de-duplication. ORDER BY signup_date on that query is rejected by most databases, because after the duplicates are removed there may be several signup dates per country and no single value to sort by. If you need that, you need GROUP BY with an aggregate — which brings us to the last section.

DISTINCT or GROUP BY?

These two queries return exactly the same rows:

SELECT DISTINCT category FROM Products;
category
Hardware
Accessories
Software
Services

4 rows · produced by running this query on the sample database

SELECT category FROM Products GROUP BY category;
category
Hardware
Accessories
Software
Services

4 rows · produced by running this query on the sample database

On a modern database they usually produce the same plan too, so the choice is about intent. Use DISTINCT when you want a de-duplicated list and nothing else. Use GROUP BY the moment you want anything computed per group:

SELECT category,
       COUNT(*)          AS products,
       MIN(unit_price)   AS cheapest,
       MAX(unit_price)   AS dearest
FROM Products
GROUP BY category
ORDER BY products DESC;
categoryproductscheapestdearest
Hardware2449.991299
Accessories2119189.5
Software28991450
Services1400400

4 rows · produced by running this query on the sample database

That is the query DISTINCT can never write, and it answers the “which city?” problem from earlier too — pick one deliberately with an aggregate rather than hoping DISTINCT guesses:

SELECT country,
       COUNT(*)  AS customers,
       MIN(city) AS first_city_alphabetically
FROM Customers
GROUP BY country
ORDER BY customers DESC, country;
countrycustomersfirst_city_alphabetically
Peru2NULL
Japan1NULL
Lebanon1NULL
Norway1NULL
Tanzania1NULL
United Kingdom1NULL
United States1NULL

7 rows · produced by running this query on the sample database

Choosing between them
You wantUse
a list of unique valuesDISTINCT
unique combinations of several columnsDISTINCT with all of them listed
how many different values there areCOUNT(DISTINCT column)
one row per group with something computedGROUP BY with aggregates
one row per group with a chosen columnGROUP BY with MIN/MAX, or a window function
to remove duplicate rows from a tablenot a SELECT at all - see deleting duplicates

That last row matters: DISTINCT changes what a query returns, never what the table contains. To fix the data itself, see finding and deleting duplicate rows.

Performance, briefly

DISTINCT is not free. To know whether a row is a duplicate the database has to compare it against the rows it has already produced, which means sorting or hashing the whole result. Two practical consequences:

  • Select fewer columns. Every extra column makes the comparison wider and duplicates rarer — often the reason a DISTINCT “stopped working” is that someone added a column.
  • A DISTINCT that fixes a join is a warning sign. If a query needs DISTINCT to get sensible numbers, the join is probably multiplying rows. Fix the join, or aggregate deliberately; papering over it with DISTINCT hides a bug and will still be wrong once someone selects an extra column.

Practice this topic

Reading explains the idea; producing it yourself is what makes it stick. These exercises use exactly what this page covers, and your answer is checked by running it against the same sample database:

See all 42 exercises →

Frequently asked questions

What does SELECT DISTINCT do in SQL?

It removes duplicate rows from the result of a query. It applies to the whole SELECT list, so SELECT DISTINCT country returns each country once, while SELECT DISTINCT country, city returns each unique country-and-city combination.

Why is SELECT DISTINCT returning duplicates?

Almost always because the SELECT list has more columns than you think. DISTINCT de-duplicates entire rows, so if any column differs the row is not a duplicate. Remove columns until only the ones that define uniqueness remain, or switch to GROUP BY.

Can I apply DISTINCT to only one column?

No. There is no DISTINCT(column) in SQL - DISTINCT is a keyword that modifies the whole SELECT list, not a function. If you want one row per value of one column while still showing others, use GROUP BY with an aggregate such as MIN(city), or a window function.

What is the difference between DISTINCT and GROUP BY?

For a plain list of unique values they return the same rows and usually the same query plan, so the choice is about intent. GROUP BY is the tool as soon as you want something computed per group - a count, a total, a minimum - which DISTINCT cannot express at all.

How does DISTINCT treat NULL?

It treats all NULLs as a single value, so a column containing many NULLs contributes exactly one NULL row to the result. This is deliberately different from = , where comparing two NULLs gives unknown rather than true.

Does DISTINCT slow down a query?

It has a real cost: the database must sort or hash the result to detect duplicates. The cost grows with the number of columns and rows. A DISTINCT added to make a joined query return the right numbers is usually hiding a join that multiplies rows, which is worth fixing properly.

About the author

SQL Practice

SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.