Skip to course content
Free SQL course

SQL for Data Analysis and AI

Unit 09.00: Case, whitespace, and the duplicate category

Unit ID: SQL-M09-U01 - Estimated active time: 14-17 minutes Objective: detect and quantify category splitting before it reaches a chart.

Two rows that look identical on screen

Module 4 showed the symptom; this unit is about finding it deliberately. Our city column:

SELECT city, COUNT(*) AS customers
FROM customers
GROUP BY city
ORDER BY customers DESC;
-- Mumbai      | 1070
-- Bengaluru   | 1069
-- Chennai     | 1069
-- New Delhi   |  535
-- New Delhi   |  535     <- trailing space
-- new delhi   |  534     <- lowercase

Three of those rows are the same city. Two of them render identically - the only difference is a trailing space, which no reader can see.

The two-number diagnostic

Never eyeball this. Compare raw and normalised distinct counts:

SELECT COUNT(DISTINCT city)              AS raw_values,
       COUNT(DISTINCT LOWER(TRIM(city))) AS real_categories
FROM customers;
-- 6 | 4

Six values, four categories. Any gap means your groupings are already wrong. One query, and it works on a column you have never seen.

Finding the specific offenders

SELECT LOWER(TRIM(city))             AS normalised,
       COUNT(DISTINCT city)          AS spelling_variants,
       COUNT(*)                      AS customers
FROM customers
GROUP BY normalised
HAVING COUNT(DISTINCT city) > 1
ORDER BY customers DESC;
-- new delhi | 3 | 1604

New Delhi has three spellings and 1,604 customers. That single row tells you what to fix and how much it matters - this is the largest city, currently reported as fourth.

Whitespace specifically

Trailing spaces are the hardest to spot, so count them directly:

SELECT COUNT(*) FROM customers WHERE city <> TRIM(city);
-- 535

535 rows carry stray whitespace. Nothing in any report would have revealed that.

Practice

Run the two-number diagnostic on country. Interpret the result, and say what it implies about which columns need this check.

Check your answer
SELECT COUNT(DISTINCT country)                AS raw_values,
       COUNT(DISTINCT LOWER(TRIM(country)))   AS real_categories
FROM customers;
-- 4 | 4

Equal, so country is clean - it holds a controlled set of codes rather than free text.

The implication: human-entered free text (city) needs the check; system-generated codes usually do not. Run it anyway - one query converts an assumption into a fact.

Takeaway

Compare COUNT(DISTINCT col) with COUNT(DISTINCT LOWER(TRIM(col))) before grouping any text column. A gap means the categories - and the ranking built on them - are already broken.

---