Tier 4 · Advanced

FILTER Clause on Aggregates in SQL

By Owen Middleton · Updated September 2026 · Examples run on PostgreSQL 17

What does the FILTER clause do in SQL?

The FILTER clause attaches a condition directly to an aggregate function, restricting which rows that specific aggregate counts or sums, while leaving every other aggregate in the query unaffected.

The scenario: you're building a regional order dashboard. For each region, you need total orders, completed orders, and completed revenue — all in one row. You already know how to do this with CASE WHEN inside an aggregate. FILTER is a cleaner way to express the same thing. Instead of embedding a CASE expression inside the function, you place the condition after it:

SELECT
    COUNT(*) AS total_orders,
    COUNT(*) FILTER (WHERE status = 'delivered') AS delivered_orders,
    SUM(total_amount) FILTER (WHERE status = 'delivered') AS delivered_revenue
FROM orders
SELECT
  COUNT(*) AS total_orders,
  COUNT(*) FILTER (WHERE status = 'delivered') AS delivered_count,
  COUNT(*) FILTER (WHERE status = 'pending') AS pending_count,
  SUM(total_amount) FILTER (WHERE status = 'delivered') AS delivered_revenue
FROM orders

How does the FILTER clause work on an aggregate?

Each FILTER is independent. COUNT(*) FILTER (WHERE status = 'delivered') only counts delivered orders. The plain COUNT(*) still counts everything. A row excluded by one FILTER is still visible to all other aggregates in the same SELECT list.

The CASE WHEN equivalent for the completed count:

COUNT(CASE WHEN status = 'completed' THEN 1 END)

Both produce the same number. FILTER is just easier to read — the condition sits right next to the function it constrains, instead of being buried inside a CASE expression wrapped around the argument.

FILTER works with every standard aggregate: COUNT, SUM, AVG, MIN, MAX, and others. When you have several conditional aggregates in one SELECT list, each targeting a different condition, FILTER makes the query much easier to scan:

SELECT
    COUNT(*) FILTER (WHERE status = 'completed') AS completed,
    COUNT(*) FILTER (WHERE status = 'pending') AS pending,
    COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled
FROM orders

The same query with CASE WHEN would have three nested expressions to parse. With FILTER, each line states its condition plainly.

How does FILTER differ from CASE WHEN inside an aggregate?

The one thing that trips people up

FILTER excludes rows from the aggregate entirely. CASE WHEN excludes them by substituting NULL, which COUNT then ignores. For COUNT, the result is the same. For AVG, it can differ in subtle ways — FILTER removes the row from both the sum and the count, while CASE WHEN removes the value from the sum but not necessarily the count, depending on how it's written. For standard AVG(CASE WHEN ... END), behavior matches FILTER. Just be deliberate about which rows you want included in each calculation.

Should you use FILTER or CASE WHEN?

When to use FILTER vs CASE WHEN

For a SELECT list with several conditional aggregates targeting different conditions, FILTER is easier to scan — each function states its condition plainly on one line. For a single conditional aggregate embedded inside a complex expression, CASE WHEN often integrates more naturally. Both are correct; the choice is about which makes the query easier for someone else to read. On teams that use PostgreSQL, FILTER is increasingly the standard for multi-condition breakdowns.

Practice FILTER Clause in SQL

Practice · easy ecommerce · Brightlane

Brightlane's operations team is preparing an order status summary for the weekly review.

Write a query to return the total number of orders, the number of delivered orders, and the number of pending orders as a single row.

Assumptions:

  • The orders table has one row per order with a status.
  • The total count covers every order. The delivered count covers only orders with status = 'delivered'. The pending count covers only orders with status = 'pending'.

Output:

  • A single row with columns total_orders, delivered_orders, and pending_orders.
Schema · ecommerce5 tables? = nullable
categories
idinteger
nametext
parent_id?integer
products
idinteger
nametext
category_id?integer
pricenumeric
stock_qtyinteger
attributes?jsonb
order_items
idinteger
order_id?integer
product_id?integer
quantityinteger
unit_pricenumeric
customers
idinteger
nametext
emailtext
city?text
countrytext
created_attimestamptz
is_activeboolean
orders
idinteger
customer_id?integer
ordered_attimestamptz
statustext
total_amountnumeric

Run previews · Check grades

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

Worked solution

The full breakdown walks through the shape, each clause, why this approach beats the alternatives, and the trap to avoid.

See the full worked solution

9 FILTER Clause practice problems

Write a query to return the total number of orders, the number of delivered orders, and the number of pending orders as a single row.

easy ecommerce

Write a query to return every customer ID, the total number of orders they have placed, and the number of those orders with status = 'delivered'.

easy ecommerce

Write a query to return every department ID, the total number of employees assigned to it, and the count of employees currently active.

easy hr

Write a query to return every customer ID, their total order count, the number of delivered orders, and the total revenue from delivered orders only.

medium ecommerce

Write a query to return every category ID, the total number of products in that category, the count of products with price greater than $100, and the average price among products with price greater than $100.

medium ecommerce

Write a query to return every user ID, the user's total event count, the count of event_type = 'purchase' events, and the count of event_type = 'checkout' events.

medium analytics

Write a query to return every customer ID, their total order count, and the number of orders that are both delivered and have total_amount greater than $200.

medium ecommerce

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.

hard ecommerce

Write a query to return every category ID, the total product count, the count of products priced above $1,000, and the combined price of those luxury products.

hard ecommerce

Start learning to practice all 9 FILTER Clause problems, with instant grading and mastery tracking.

Deeper guides on FILTER Clause

Common questions about FILTER Clause

Does FILTER work with every aggregate?

Yes, with counts, sums, averages and the extremes alike. Each aggregate in the SELECT list carries its own FILTER, and a row excluded by one is still visible to the others, which is what lets several differently-filtered figures share one pass over the table.

Can you use FILTER together with GROUP BY?

Yes, and that is where it earns its keep. Each group gets its own filtered and unfiltered figures side by side, so one row per customer can show their total orders next to their delivered ones.

What does a FILTER that matches nothing return?

A count of zero and a sum of NULL, following the ordinary aggregate rules. The difference matters in a report, because a zero reads as a measurement and a NULL reads as a gap, and only one of them is what you meant.

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.