N049-H1 Tier 4 · Advanced · hard ecommerce · Brightlane

Return every country, the total number of customers in that country, the count of customers with a missing `city`, and the count of customers with a recorded `city`

Part of FILTER Clause on Aggregates in SQL

The problem

Brightlane's data quality team is auditing the customer registry to understand how many records are missing a city value, broken down by country.

Write a query to return every country, the total number of customers in that country, the count of customers with a missing city, and the count of customers with a recorded city.

Assumptions:

  • Each country value with at least one customer should appear once.
  • For each country, the total count covers every customer in that country. The missing-city count covers only customers whose city is missing. The with-city count covers only customers whose city is recorded.
  • The missing-city count plus the with-city count equals the total count for each country (the two are mutually exclusive partitions of the total).

Output:

  • One row per country, with columns country, total_customers, missing_city_count, and with_city_count.
Schema · ecommerce 5 tables
categories
id integer
name text
parent_id? integer
products
id integer
name text
category_id integer
price numeric
stock_qty integer
attributes? jsonb
order_items
id integer
order_id integer
product_id integer
quantity integer
unit_price numeric
customers
id integer
name text
email text
city? text
country text
created_at timestamptz
is_active boolean
orders
id integer
customer_id integer
ordered_at timestamptz
status text
total_amount numeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
SELECT
  country,
  COUNT(*) AS total_customers,
  COUNT(*) FILTER (
    WHERE
      city IS NULL
  ) AS missing_city_count,
  COUNT(*) FILTER (
    WHERE
      city IS NOT NULL
  ) AS with_city_count
FROM
  customers
GROUP BY
  country

The shape

Two complementary FILTER conditions partition each country's customer set into the missing-city subset and the recorded-city subset. COUNT(*) FILTER (WHERE city IS NULL) and COUNT(*) FILTER (WHERE city IS NOT NULL) are mutually exclusive and together cover every row, so their sum equals the unfiltered COUNT(*) on every country row — which is exactly the audit invariant the data quality team needs to confirm.

Clause by clause

  • SELECT country, COUNT(*) AS total_customers, COUNT(*) FILTER (WHERE city IS NULL) AS missing_city_count, COUNT(*) FILTER (WHERE city IS NOT NULL) AS with_city_count returns the country, the full customer count, and the two NULL-partitioning counts. The first FILTER keeps only the rows whose city is missing; the second keeps only the rows whose city is recorded. Every customer in the partition contributes to exactly one of the two filtered counts.
  • FROM customers reads the customer records.
  • GROUP BY country partitions the rows per country. The three counts evaluate inside each country's partition independently.

Why IS NULL / IS NOT NULL and not city = NULL / city != NULL

city = NULL does not return true when city is NULL. It returns NULL, which the FILTER treats as not-true, exactly the same way WHERE treats a NULL predicate. The filter would drop every row, the missing-city count would always be zero, and the audit invariant would still appear to hold because both counts would be wrong in matched ways. IS NULL and IS NOT NULL are the only operators that actually test for the presence or absence of a NULL value. They return real booleans, never NULL, so the FILTER knows which rows to keep.

The trap

The two FILTER counts have to sum to the unfiltered COUNT(*) on every row, and they do here because IS NULL and IS NOT NULL are exhaustive — every value is exactly one of the two. Writing the second filter as WHERE city = '' instead of WHERE city IS NOT NULL would silently miss any customer whose city is recorded as a non-empty string, and the audit invariant would break without raising an error. The fix is to use the NULL-aware operators directly, never an equality check, whenever the question is about presence rather than value.

You practiced FILTER (WHERE col IS NULL) and FILTER (WHERE col IS NOT NULL) as complementary counts — two FILTER clauses partitioning a record set into the missing-value and recorded-value subsets, alongside the total.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.