SQL SELECT DISTINCT: Remove Duplicates Correctly

SELECT DISTINCT removes duplicate rows from a query result by comparing the complete combination of selected expressions.

Lesson 28 of 102Beginner10 min read

SELECT DISTINCT removes duplicate rows from a query result by comparing the complete combination of selected expressions.

A list of customer cities should not repeat Leeds for every customer. DISTINCT answers that request, but it does not choose a representative customer or repair a join that multiplied rows. Its effect depends on precisely which columns you select.

This reference adds detail to the main course. You can read it independently: the small database below supplies every table and row used in the examples. Start with the question each query answers, then compare the returned values with the data rather than memorising a line of syntax.

Set up the practice database

Run this setup once in a new practice database, before the examples. It uses four customers and six sales. Ana has two sales, Ben has one, Cara has two, and Dev has none.

Sale 106 has no customer link; sale 104 has an unknown amount. These deliberate test cases expose missing information and unmatched relationships.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name VARCHAR(40) NOT NULL,
  city VARCHAR(40),
  manager_id INTEGER
);
CREATE TABLE sales (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER,
  amount DECIMAL(10,2),
  status VARCHAR(20) NOT NULL,
  created_on DATE NOT NULL
);
INSERT INTO customers (id,name,city,manager_id) VALUES
  (1,'Ana','Leeds',NULL),(2,'Ben','Leeds',1),
  (3,'Cara','Porto',1),(4,'Dev',NULL,2);
INSERT INTO sales (id,customer_id,amount,status,created_on) VALUES
  (101,1,10,'paid','2026-01-01'),
  (102,1,30,'pending','2026-01-02'),
  (103,2,20,'paid','2026-01-02'),
  (104,3,NULL,'pending','2026-02-01'),
  (105,3,20,'paid','2026-02-02'),
  (106,NULL,5,'paid','2026-02-03');

An amount of zero would be a known value. The NULL amount here means the value is unknown. A customer with no matching sale differs from a sale whose customer_id is NULL.

The primary keys identify individual records even when amounts or cities repeat. Dates use year-month-day format. The examples specify sorting whenever order matters; insertion order alone does not guarantee display order.

How SELECT DISTINCT works

The database first forms the selected row values and then removes repeats from that result. SELECT DISTINCT city compares cities, while SELECT DISTINCT city, name compares city-and-name pairs. Ana and Ben share Leeds but have different names, so both pairs remain. Adding an identifier usually makes every selected row unique. This is why DISTINCT id, city does not produce one row per city when id is a primary key.

Duplicate elimination treats NULL values as the same for this operation. Multiple unknown cities become one NULL result even though ordinary NULL = NULL comparisons are unknown. This special result-set behaviour does not make NULL a normal comparable value everywhere else. Empty strings and NULL are still different values. Collation also matters for text: a case-insensitive collation may consider two differently cased labels equal, while another collation keeps both.

Ordering distinct values and NULL

DISTINCT has no sorting promise. A database might sort internally to remove duplicates, or it might use hashing; neither implementation is an output-order contract. Add ORDER BY for presentation. A useful portable way to place unknown values last is ordering by CASE WHEN city IS NULL THEN 1 ELSE 0 END, then city, outside a derived table containing the distinct values. Separating these steps avoids dialect restrictions on ordering a DISTINCT query by an unselected expression.

Use COUNT(DISTINCT city) when the question asks for the number of different known cities. In this dataset that is two, because COUNT with a column ignores NULL. SELECT DISTINCT city returns three rows because it includes one unknown city. The two queries answer different questions. For the latest sale per customer, use an appropriate ranking query rather than DISTINCT customer_id, created_on. Distinct date-and-customer pairs are not automatically the latest pair. If unexpected duplicates originate from a join, inspect the matching keys before adding DISTINCT, which can conceal a relationship error.

MySQL, PostgreSQL, SQL Server and SQLite

DISTINCT works across all four systems. PostgreSQL DISTINCT ON is an additional feature with different syntax and is not portable. Multi-expression COUNT(DISTINCT ...) also differs by engine; select distinct pairs in a subquery and count that result if you need a portable pair count.

Check the engine-specific syntax and version requirements in SQLite DISTINCT processing.

Practice task and worked solution

Return the number of distinct known customer cities. Do not count the unknown city as a named location.

Try the task before reading its worked solution. The supplied rows make the expected answer checkable by hand.

SQL
SELECT COUNT(DISTINCT city) AS known_cities FROM customers;
Output
known_cities
2

There are two different non-NULL labels, Leeds and Porto. The second Leeds row does not increase the distinct count, and Dev’s missing city is excluded by COUNT(city). Contrast this scalar answer with the three-row city list in the earlier example.

Change the city-list SELECT to DISTINCT city,name and predict four rows, because no city-name pair repeats. Then count DISTINCT city and explain why its answer is two rather than the three-row city list. Add another customer with a missing city in a disposable copy: the list should still have one NULL while the count of known cities remains unchanged.

SQL SELECT DISTINCT: Remove Duplicates Correctly Syntax

SELECT DISTINCT

SELECT city FROM (
  SELECT DISTINCT city FROM customers
) AS cities
ORDER BY CASE WHEN city IS NULL THEN 1 ELSE 0 END, city;

SQL SELECT DISTINCT: Remove Duplicates Correctly Summary

  • SELECT DISTINCT removes repeated rows from the result, comparing the full combination of selected columns.
  • DISTINCT city, name keeps every different pair, so adding an id column usually makes every row unique.
  • DISTINCT treats all NULLs as one value, while COUNT(DISTINCT col) ignores NULL completely.
  • DISTINCT doesn't sort; add ORDER BY for a predictable order.
  • It can't fix a join that returns the wrong grain; choose the right rows or use GROUP BY.

SQL SELECT DISTINCT: Remove Duplicates Correctly Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Unique customer cities with NULL last

The inner query produces the unique city values. The outer query puts known cities in alphabetical order and the missing city last. Two Leeds customer rows become one Leeds output row; Dev contributes the NULL result.

SQL
SELECT city FROM (
  SELECT DISTINCT city FROM customers
) AS cities
ORDER BY CASE WHEN city IS NULL THEN 1 ELSE 0 END, city;
Output
city
Leeds
Porto
NULL

Common SQL SELECT DISTINCT: Remove Duplicates Correctly Mistakes

Using DISTINCT to hide join duplicates

Adding DISTINCT to SELECT customers.id, sales.id after a one-to-many join will not merge a customer’s different sales, because the selected sale ids differ. Decide whether you need customers, sales, or customer totals. Select the intended grain or use grouping rather than hoping DISTINCT recognises the business entity.

SQL SELECT DISTINCT: Remove Duplicates Correctly in Real Projects

City filters in a checkout form

A checkout filter can offer each known delivery city once. That display list can use DISTINCT city, but the application should decide separately whether to offer an Unknown option. Exposing an unknown city as the text NULL is a presentation decision and should not alter the stored value.

For a duplicate-free dropdown, project only the category key and the approved display label. For a data-quality investigation, retain record ids so duplicates remain traceable. Those two outputs have opposite purposes. A DISTINCT that hides duplicated relationship matches can make a label list look correct while leaving a revenue calculation wrong.

SQL SELECT DISTINCT: Remove Duplicates Correctly FAQ

Does DISTINCT modify the table?

No. It affects only the rows returned by that query; the stored rows, including duplicates, stay exactly as they were. To remove duplicate rows from a table permanently, identify them and delete them, as the deduplication lesson shows.

Can DISTINCT select one arbitrary row per customer?

No. Selecting several columns deduplicates their full combination, so a customer with two different sales still appears twice. Use a ranking or grouping rule when you need one chosen row, such as ROW_NUMBER() to keep each customer's latest sale.

Is DISTINCT faster than GROUP BY?

For a plain list of unique values, they usually produce the same plan. Performance depends on the query and the available indexes. Choose the operation that expresses the required result, DISTINCT for unique rows and GROUP BY when you also need aggregates, and measure on realistic data.

Does DISTINCT apply to only the first column?

No. DISTINCT always applies to the whole selected row, wherever it is written, so SELECT DISTINCT city, name compares city-and-name pairs. PostgreSQL's DISTINCT ON (city) keeps one row per city, but it's PostgreSQL-only syntax.

How do I count unique combinations of two columns?

Portable SQL counts the rows of a distinct subquery: SELECT COUNT(*) FROM (SELECT DISTINCT city, name FROM customers) AS pairs;. MySQL also accepts COUNT(DISTINCT city, name), but PostgreSQL and SQLite don't.

SQL SELECT DISTINCT: Remove Duplicates Correctly Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

How many rows does the distinct city list return, including its NULL row?

SQL SELECT DISTINCT: Remove Duplicates Correctly Quiz

2 questions to check what you learned.

  1. What does DISTINCT city, name compare?

    • Only city, because it is selected first
    • The combined city-and-name values
    • Each selected column independently rather than the complete pair
  2. What is COUNT(DISTINCT city) for this dataset?

    • Three because the distinct NULL counts as a known city
    • Four because both Leeds customers count separately
    • 2 because it ignores NULL and counts Leeds and Porto once