When working with SQL, you’ll often need to count rows that meet specific conditions rather than simply tallying all records in a table. This is where combining COUNT with CASE WHEN becomes powerful. Instead of writing multiple queries or filtering data repeatedly, you can calculate conditional counts in a single query. For example, you can track how many orders were shipped, how many were pending, or how many customers fall into different age groups—all within the same result set.
One big advantage is efficiency: using COUNT
with CASE WHEN
avoids writing separate subqueries or joins for each condition. That means the database engine only scans the data once, saving both memory and computation time. Other methods, like running multiple queries or creating temporary tables often require storing intermediate results, which increases memory usage.
This approach is widely used in reporting, dashboards, and analytics because it makes your queries both more efficient and easier to interpret. By using conditional logic inside aggregate functions, you gain flexibility in slicing data by categories or conditions without creating multiple subqueries.
The COUNT
function is one of the most common tools in SQL, and pairing it with a CASE WHEN
expression unlocks the ability to count rows conditionally. Instead of returning a simple total, you can apply logic directly inside the aggregate to include only rows that meet specific criteria.
Let’s take a look at how COUNT
works with CASE WHEN
.
The basic structure looks like this:
COUNT(CASE WHEN condition THEN 1 END)
Here’s what happens: if the condition is true, the CASE
returns 1
, and the COUNT
adds it to the count. If the condition is false, the CASE
returns NULL
, which is ignored by COUNT
.
The trick is that COUNT
only counts non-NULL values. Because the CASE
produces NULL
when the condition isn’t met, those rows are automatically left out of the count.
For example, if you want to compare shipped orders with the total number of orders:
SELECT
COUNT(*) AS total_orders,
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
This query shows both the total number of orders and how many were shipped—all in one result set.
Conditional counting with CASE WHEN
in SQL is a practical way to break data into meaningful categories without writing multiple queries. Instead of just returning totals, you can apply business rules directly inside your aggregates to count only rows that meet specific conditions. This technique is widely used in scenario based SQL questions across industries—from e-commerce and marketing to HR and customer support to track performance metrics, segment users, and monitor operations in a single, efficient query.
In an e-commerce orders
table, you might want to see how many orders have been shipped vs not shipped.
Table: orders
order_id | status |
---|---|
1 | shipped |
2 | pending |
3 | shipped |
4 | canceled |
5 | shipped |
SELECT
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders,
COUNT(CASE WHEN status != 'shipped' THEN 1 END) AS unshipped_orders
FROM orders;
Output:
shipped_orders | unshipped_orders |
---|---|
3 | 2 |
This gives you a quick breakdown of logistics performance—useful for tracking fulfillment rates.
If your users
table tracks signup_channel
(e.g., "web"
, "mobile"
, "referral"
), you can count active users by channel:
Table: users
user_id | signup_channel | active |
---|---|---|
1 | web | 1 |
2 | mobile | 0 |
3 | referral | 1 |
4 | web | 1 |
5 | mobile | 1 |
SELECT
COUNT(CASE WHEN signup_channel = 'web' AND active = 1 THEN 1 END) AS web_active,
COUNT(CASE WHEN signup_channel = 'mobile' AND active = 1 THEN 1 END) AS mobile_active,
COUNT(CASE WHEN signup_channel = 'referral' AND active = 1 THEN 1 END) AS referral_active
FROM users;
Output:
web_active | mobile_active | referral_active |
---|---|---|
2 | 1 | 1 |
This helps measure marketing ROI by comparing which channels bring the most active users.
Suppose an employees
table has department
values like "HR"
, "IT"
, "Sales"
. You can summarize headcount by department:
Table: employees
emp_id | department |
---|---|
1 | HR |
2 | Sales |
3 | IT |
4 | IT |
5 | Sales |
SELECT
COUNT(CASE WHEN department = 'HR' THEN 1 END) AS hr_count,
COUNT(CASE WHEN department = 'IT' THEN 1 END) AS it_count,
COUNT(CASE WHEN department = 'Sales' THEN 1 END) AS sales_count
FROM employees;
Output:
hr_count | it_count | sales_count |
---|---|---|
1 | 2 | 2 |
This is a simple but powerful way to create staffing dashboards without complex joins.
In a support system, tickets might have a priority
and a status
. You can count multiple conditions at once:
Table: tickets
ticket_id | priority | status |
---|---|---|
1 | urgent | open |
2 | urgent | closed |
3 | normal | delayed |
4 | urgent | open |
5 | normal | open |
SELECT
COUNT(CASE WHEN priority = 'urgent' AND status = 'open' THEN 1 END) AS urgent_open,
COUNT(CASE WHEN priority = 'urgent' AND status = 'closed' THEN 1 END) AS urgent_closed,
COUNT(CASE WHEN priority = 'normal' AND status = 'delayed' THEN 1 END) AS normal_delayed
FROM tickets;
This allows teams to monitor SLAs by quickly surfacing how many critical tickets are unresolved or overdue.
Output:
urgent_open | urgent_closed | normal_delayed |
---|---|---|
2 | 1 | 1 |
COUNT
and SUM
are the two most common ways to apply conditional aggregation with CASE WHEN
, but they behave differently depending on what you need. Understanding when to use each helps you avoid silent errors and write cleaner, more accurate queries.
Use COUNT
when you want the number of rows meeting a condition.
COUNT(CASE WHEN condition THEN 1 END)
-- counts TRUEs; FALSE -> NULL -> ignored
COUNT(CASE WHEN condition THEN some_nonnull_col END)
COUNT(*)
counts all rows (no condition).Use SUM
when you want a tally or weighted total—it can count like COUNT
, but can also sum numbers.
SUM(CASE WHEN condition THEN 1 ELSE 0 END)
SUM(CASE WHEN condition THEN amount ELSE 0 END)
COUNT(expr)
ignores NULL and returns 0 if no rows match.
SUM(expr)
ignores NULL, but if all inputs are NULL (i.e., no matches and no ELSE 0), result is NULL.
→ Prefer ELSE 0
(or wrap with COALESCE
) to ensure a numeric 0.
Sample table: orders
order_id | status | amount |
---|---|---|
1 | shipped | 120.00 |
2 | pending | 80.00 |
3 | shipped | 50.00 |
4 | canceled | 40.00 |
5 | shipped | 60.00 |
COUNT
vs SUM
SELECT
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_count_via_count,
SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped_count_via_sum
FROM orders;
Output
shipped_count_via_count | shipped_count_via_sum |
---|---|
3 | 3 |
Both return 3. (SUM
uses ELSE 0
to guarantee 0 when none match.)
ELSE 0
mattersSELECT
COUNT(CASE WHEN status = 'refunded' THEN 1 END) AS refunded_count_via_count,
SUM(CASE WHEN status = 'refunded' THEN 1 END) AS refunded_count_via_sum_null,
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) AS refunded_count_via_sum_zero
FROM orders;
Output
refunded_count_via_count | refunded_count_via_sum_null | refunded_count_via_sum_zero |
---|---|---|
0 | NULL |
COUNT
returns 0; SUM
without ELSE 0
returns NULL; SUM
with ELSE 0
returns 0.
SUM
is required)SELECT
SUM(CASE WHEN status = 'shipped' THEN amount ELSE 0 END) AS shipped_revenue,
SUM(CASE WHEN status <> 'shipped' THEN amount ELSE 0 END) AS unshipped_value
FROM orders;
Output
shipped_revenue | unshipped_value |
---|---|
230.00 | 120.00 |
Here SUM
computes money totals; COUNT
can’t do this.
Rules of thumb
COUNT(CASE WHEN … THEN 1 END)
or SUM(CASE WHEN … THEN 1 ELSE 0 END)
; both are fine—just remember the ELSE 0
with SUM
.SUM(CASE WHEN … THEN value ELSE 0 END)
.ELSE 0
or wrap with COALESCE(…, 0)
.This section explains how spreadsheet-style COUNTIF
functions compare to SQL’s approach for conditional counting. You’ll see why SQL doesn’t have a direct COUNTIF
, what alternatives exist, and how different databases handle conditional counts in practice.
In Excel or Google Sheets, COUNTIF
is a built-in function that counts cells matching a condition:
=COUNTIF(A2:A10, "shipped")
This formula counts all rows in A2:A10
where the value equals "shipped"
. It’s simple because spreadsheets are optimized for column-based calculations.
In SQL, however, there’s no direct COUNTIF
function. Instead, you express the same logic using CASE WHEN
inside COUNT
or SUM
.
COUNT
only counts non-NULL values, so combining it with a conditional CASE WHEN
expression naturally achieves the same as COUNTIF
.Example (e-commerce orders
table):
SELECT
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
Equivalent to Excel’s:
=COUNTIF(status_range, "shipped")
MySQL / PostgreSQL
Both support COUNT(CASE WHEN … THEN 1 END)
and SUM(CASE WHEN … THEN 1 ELSE 0 END)
as the standard way:
-- Count shipped orders
SELECT
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_orders
FROM orders;
SQL Server
In addition to the CASE
method, SQL Server supports the shorthand COUNT(*) FILTER (WHERE …)
starting from SQL Server 2017 (and PostgreSQL has long supported it):
-- PostgreSQL or SQL Server (2017+)
SELECT
COUNT(*) FILTER (WHERE status = 'shipped') AS shipped_orders,
COUNT(*) FILTER (WHERE status = 'pending') AS pending_orders
FROM orders;
This syntax is closer in spirit to COUNTIF
because the filter condition is explicit.
Key takeaway:
COUNTIF
is a simple function.COUNT(CASE WHEN …)
or SUM(CASE WHEN …)
(portable), or COUNT(*) FILTER (WHERE …)
(if supported).This section highlights how different SQL platforms and query languages handle conditional counting. While COUNT(CASE WHEN …)
is the standard approach, databases like MySQL, PostgreSQL, SQL Server, Oracle, and even JPQL each offer shorthand functions or dialect-specific alternatives that can make queries more concise or better aligned with their ecosystems.
CASE WHEN
vs IF()
In MySQL, you can use both CASE
and the simpler IF()
function.
-- Using CASE WHEN
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
-- Using IF() inside COUNT
SELECT COUNT(IF(status = 'shipped', 1, NULL)) AS shipped_orders
FROM orders;
Both return the same result. IF()
is shorter but not portable outside MySQL.
CASE WHEN
vs FILTER (WHERE …)
PostgreSQL supports FILTER (WHERE …)
for aggregate functions, which makes syntax more readable.
-- Standard CASE WHEN
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
-- With FILTER
SELECT COUNT(*) FILTER (WHERE status = 'shipped') AS shipped_orders
FROM orders;
The FILTER
version is especially useful when you want multiple conditions side by side.
CASE WHEN
vs IIF()
SQL Server has IIF()
as shorthand for simple conditions (since 2012).
-- Using CASE WHEN
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
-- Using IIF()
SELECT SUM(IIF(status = 'shipped', 1, 0)) AS shipped_orders
FROM orders;
Note: IIF()
always requires both true and false branches, so you typically wrap it in SUM()
instead of COUNT()
.
CASE WHEN
vs DECODE()
Oracle supports DECODE()
, an older function often used for conditional counting.
-- Using CASE WHEN
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_orders
FROM orders;
-- Using DECODE
SELECT SUM(DECODE(status, 'shipped', 1, 0)) AS shipped_orders
FROM orders;
DECODE(expr, search, result, default)
is less flexible than CASE
, but still widely used in legacy Oracle code.
jpql select case when count(r) > 0
)JPQL (Java Persistence Query Language) doesn’t support aggregate filters directly, but you can use CASE WHEN
with COUNT()
inside a SELECT
.
SELECT CASE WHEN COUNT(r) > 0
THEN TRUE
ELSE FALSE
END
FROM Reservation r
WHERE r.status = 'CONFIRMED'
This query returns a boolean-like result indicating whether any confirmed reservations exist. It mimics the SQL conditional count pattern in ORM queries.
This section explores how to take conditional counting beyond the basics. You’ll see how to calculate percentages, categorize multiple conditions at once, apply window functions for per-group insights, and combine COUNT CASE WHEN
with GROUP BY
and HAVING
to build richer analytics.
You can use COUNT CASE WHEN
with a denominator to calculate percentages.
Table: invoices
invoice_id | status |
---|---|
1 | paid |
2 | unpaid |
3 | paid |
4 | overdue |
5 | paid |
Query:
SELECT
COUNT(CASE WHEN status = 'paid' THEN 1 END) * 100.0 / COUNT(*) AS pct_paid
FROM invoices;
Output:
pct_paid
60.0
This shows 60% of invoices are paid.
Handle multiple categories at once by defining multiple CASE WHEN
expressions.
SELECT
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_count,
COUNT(CASE WHEN status = 'unpaid' THEN 1 END) AS unpaid_count,
COUNT(CASE WHEN status = 'overdue' THEN 1 END) AS overdue_count
FROM invoices;
Output:
paid_count | unpaid_count | overdue_count |
---|---|---|
3 | 1 | 1 |
This mimics pivot-table style summaries directly in SQL.
Combine CASE WHEN
with window functions to add conditional counts across partitions (e.g., per customer).
Table: orders
order_id | customer_id | status |
---|---|---|
1 | 101 | shipped |
2 | 101 | pending |
3 | 102 | shipped |
4 | 102 | shipped |
5 | 103 | canceled |
Query:
SELECT
order_id,
customer_id,
status,
COUNT(CASE WHEN status = 'shipped' THEN 1 END)
OVER (PARTITION BY customer_id) AS shipped_per_customer
FROM orders;
Output:
order_id | customer_id | status | shipped_per_customer |
---|---|---|---|
1 | 101 | shipped | 1 |
2 | 101 | pending | 1 |
3 | 102 | shipped | 2 |
4 | 102 | shipped | 2 |
5 | 103 | canceled | 0 |
This helps identify how many shipped orders each customer has without collapsing rows.
Conditional counting pairs naturally with GROUP BY
and HAVING
for analytics.
Table: tickets
ticket_id | agent | status |
---|---|---|
1 | Alice | open |
2 | Alice | closed |
3 | Bob | open |
4 | Bob | open |
5 | Bob | closed |
Query:
SELECT
agent,
COUNT(CASE WHEN status = 'open' THEN 1 END) AS open_tickets,
COUNT(CASE WHEN status = 'closed' THEN 1 END) AS closed_tickets
FROM tickets
GROUP BY agent
HAVING COUNT(CASE WHEN status = 'open' THEN 1 END) > 1;
Output:
agent | open_tickets | closed_tickets |
---|---|---|
Bob | 2 | 1 |
This finds agents with more than one open ticket.
Efficient use of COUNT(CASE WHEN …)
is beyond basic syntax, it is about performance, readability, and scalability. This section explores optimization strategies, from choosing between COUNT
vs SUM
, leveraging indexes, and simplifying queries with CTEs or materialized views, to taking advantage of dialect-specific features like PostgreSQL’s FILTER
. These practices help keep conditional counts accurate, fast, and maintainable even on large datasets.
Both COUNT(CASE WHEN …)
and SUM(CASE WHEN …)
achieve the same result for conditional counts. But they differ based on the following aspects.
Aspect | COUNT(CASE WHEN …) |
SUM(CASE WHEN …) |
---|---|---|
NULL handling | Skips NULL by default |
Needs ELSE 0 (or COALESCE ) for predictable results |
Readability | Cleaner for pure counts | Clearer when mixing counts + numeric totals |
Performance | Slightly faster in some cases; both usually optimized equivalently | Equivalent performance in most databases |
Best use case | Simple conditional counts | Counts + conditional numeric sums together |
Rule of thumb: pick the form that makes intent clearer to future readers of your SQL.
Indexes can drastically improve conditional counts, especially when filtering frequently.
Single-column indexes: If you often count status = 'shipped'
, an index on status
will speed up lookups.
Composite indexes: For conditions like status = 'shipped' AND customer_id = 101
, use a composite index (status, customer_id)
.
Partial indexes (PostgreSQL):
CREATE INDEX idx_orders_shipped ON orders(order_id) WHERE status = 'shipped';
This optimizes queries counting only shipped rows. Indexes help reads but add overhead to writes—balance based on workload.
When your query involves multiple conditional counts, Common Table Expressions (CTEs) or materialized views can reduce duplication.
WITH base AS (
SELECT customer_id, status
FROM orders
)
SELECT
customer_id,
COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped_count,
COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending_count
FROM base
GROUP BY customer_id;
CTEs make queries easier to read and maintain.
Materialized views are pre-computed, making repeated conditional counts much faster in reporting dashboards.
FILTER
vs CASE WHEN performancePostgreSQL offers both CASE WHEN
and FILTER (WHERE …)
.
-- CASE WHEN
COUNT(CASE WHEN status = 'shipped' THEN 1 END)
-- FILTER
COUNT(*) FILTER (WHERE status = 'shipped')
FILTER
is shorter and easier to scan when you need many conditions.FILTER
can be slightly more efficient because the condition applies directly during aggregation, skipping unnecessary expression evaluation.For Postgres, prefer FILTER
in reporting queries. For cross-platform SQL, stick with CASE WHEN
.
Conditional counts are powerful, but subtle mistakes can lead to misleading results. These best practices help ensure accuracy and clarity.
Handling NULL values is an essential aspect of most SQL queries. By default, COUNT(expr)
ignores NULL
. This works in your favor most of the time, but can also hide missing data.
Example – counting active users
SELECT
COUNT(CASE WHEN active = 1 THEN 1 END) AS active_users,
COUNT(CASE WHEN active = 0 THEN 1 END) AS inactive_users
FROM users;
If active
is NULL
for some users, those rows are skipped entirely. If you want to treat NULL
explicitly:
SELECT
COUNT(CASE WHEN active = 1 THEN 1 END) AS active_users,
COUNT(CASE WHEN active = 0 THEN 1 END) AS inactive_users,
COUNT(CASE WHEN active IS NULL THEN 1 END) AS unknown_status
FROM users;
Tip: Always decide whether NULL
should mean “unknown” (count separately) or “inactive” (fold into another category).
When writing multiple CASE WHEN
conditions, make sure categories don’t unintentionally overlap.
Bad (overlap):
SELECT
COUNT(CASE WHEN amount > 100 THEN 1 END) AS high_value,
COUNT(CASE WHEN amount > 50 THEN 1 END) AS mid_value
FROM orders;
Here, a row with amount = 120
is counted in both categories.
Good (mutually exclusive):
SELECT
COUNT(CASE WHEN amount > 100 THEN 1 END) AS high_value,
COUNT(CASE WHEN amount BETWEEN 51 AND 100 THEN 1 END) AS mid_value
FROM orders;
Tip: Write conditions so each row fits into exactly one bucket, unless intentional.
A common pattern is to track events by time intervals.
Table: logins
user_id | login_date |
---|---|
1 | 2025-09-01 |
2 | 2025-09-01 |
3 | 2025-09-02 |
1 | 2025-09-03 |
Query – daily active users:
SELECT
login_date,
COUNT(DISTINCT user_id) AS dau
FROM logins
GROUP BY login_date
ORDER BY login_date;
Output:
login_date | dau |
---|---|
2025-09-01 | 2 |
2025-09-02 | 1 |
2025-09-03 | 1 |
Query – conditional monthly counts (active vs inactive):
SELECT
DATE_TRUNC('month', login_date) AS month,
COUNT(DISTINCT CASE WHEN active = 1 THEN user_id END) AS active_users,
COUNT(DISTINCT CASE WHEN active = 0 THEN user_id END) AS inactive_users
FROM users_log
GROUP BY DATE_TRUNC('month', login_date)
ORDER BY month;
Time-based aggregations are where CASE WHEN
+ GROUP BY
really shine in analytics dashboards.
The best way to master conditional counting is by solving hands-on and case study type problems. This section walks through real-world scenarios like tracking employees by type, shipments by delivery status, or users by verification flag, where COUNT(CASE WHEN …)
turns business rules into clear, actionable SQL queries. Each exercise includes prompts, hints, and expected outputs to help you practice interview-style thinking.
Table: employees
emp_id | dept | employment_type |
---|---|---|
1 | HR | full-time |
2 | HR | part-time |
3 | IT | full-time |
4 | IT | full-time |
5 | Sales | part-time |
Prompt: Write a SQL query to count the number of full-time and part-time employees in each department.
Hint:
CASE WHEN
because you’re applying different conditions inside the same aggregation.dept
to split counts per department.Query
SELECT
dept,
COUNT(CASE WHEN employment_type = 'full-time' THEN 1 END) AS full_time_count,
COUNT(CASE WHEN employment_type = 'part-time' THEN 1 END) AS part_time_count
FROM employees
GROUP BY dept
ORDER BY dept;
Expected Output
dept | full_time_count | part_time_count |
---|---|---|
HR | 1 | 1 |
IT | 2 | 0 |
Sales | 0 | 1 |
Using CASE WHEN
inside COUNT
lets you calculate multiple conditional aggregates in a single scan, instead of running separate queries.
Table: shipments
id | expected_date | delivered_date |
---|---|---|
1 | 2025-09-01 | 2025-09-02 |
2 | 2025-09-05 | 2025-09-04 |
3 | 2025-09-15 | 2025-09-20 |
4 | 2025-10-01 | 2025-10-01 |
5 | 2025-10-05 | 2025-10-07 |
Prompt: For each month (by expected_date
), count delayed (delivered_date > expected_date
) vs on-time shipments.
Hint:
expected_date
.CASE WHEN
buckets; GROUP BY
the month.Query:
SELECT
DATE_TRUNC('month', expected_date) AS month,
COUNT(CASE WHEN delivered_date > expected_date THEN 1 END) AS delayed,
COUNT(CASE WHEN delivered_date <= expected_date THEN 1 END) AS on_time
FROM shipments
GROUP BY DATE_TRUNC('month', expected_date)
ORDER BY month;
Expected Output
month | delayed | on_time |
---|---|---|
2025-09-01 | 2 | 1 |
2025-10-01 | 1 | 1 |
CASE WHEN
lets you pivot conditions into columns while grouping by time in one concise query.
Table: users
user_id | verified |
---|---|
1 | 1 |
2 | 0 |
3 | NULL |
4 | 1 |
5 | 0 |
Prompt: Count verified and non-verified users (treat NULL as non-verified).
Hint:
NULL
up front.CASE WHEN
; no GROUP BY
needed unless you want per-segment splits.Query
SELECT
COUNT(CASE WHEN verified = 1 THEN 1 END) AS verified_users,
COUNT(CASE WHEN verified = 0 OR verified IS NULL THEN 1 END) AS non_verified_users
FROM users;
Expected Output
verified_users | non_verified_users |
---|---|
2 | 3 |
You get explicit control of NULLs and clear category totals in a single statement.
Table: orders
order_id | customer_id | amount |
---|---|---|
1 | 101 | 500 |
2 | 101 | 600 |
3 | 102 | 300 |
4 | 103 | 1200 |
5 | 104 | 200 |
Prompt: Count how many distinct customers have total purchases > 1000.
Hint:
HAVING SUM(amount) > 1000
.Query:
SELECT COUNT(*) AS customers_over_1000
FROM (
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 1000
) t;
Expected Output
customers_over_1000
2
Combining GROUP BY + HAVING
yields a set of qualified customers you can count, join, or analyze further.
Table: tickets
id | priority |
---|---|
1 | urgent |
2 | high |
3 | urgent |
4 | normal |
5 | low |
Prompt: Count tickets for each priority level (urgent/high/normal/low).
Hint:
CASE WHEN
per level.agent
and GROUP BY agent
.Query
SELECT
COUNT(CASE WHEN priority = 'urgent' THEN 1 END) AS urgent_count,
COUNT(CASE WHEN priority = 'high' THEN 1 END) AS high_count,
COUNT(CASE WHEN priority = 'normal' THEN 1 END) AS normal_count,
COUNT(CASE WHEN priority = 'low' THEN 1 END) AS low_count
FROM tickets;
Expected Output
urgent_count | high_count | normal_count | low_count |
---|---|---|---|
2 | 1 | 1 | 1 |
CASE WHEN
acts like a compact pivot, turning label-based conditions into side-by-side KPIs.
Now let’s practice more real SQL interview questions!
Yes. Use conditional aggregation—COUNT
over a CASE
that returns a non-NULL only when the condition is true.
-- Shipped orders only
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped
FROM orders;
Excel/Sheets have COUNTIF(range, condition)
.
SQL has no COUNTIF
; you replicate it with:
-- Equivalent of COUNTIF(status = 'shipped')
SELECT COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped
FROM orders;
Same idea, just expressed with CASE WHEN
inside an aggregate.
Yes, COUNT with CASE WHEN is portable everywhere.
COUNT(IF(condition,1,NULL))
or SUM(condition)
(TRUE=1/FALSE=0).COUNT(*) FILTER (WHERE condition)
.CASE
(or legacy DECODE
) with COUNT
/SUM
. (No FILTER
clause.)Examples:
-- MySQL
SELECT COUNT(IF(status='shipped',1,NULL)) AS shipped FROM orders;
-- PostgreSQL
SELECT COUNT(*) FILTER (WHERE status='shipped') AS shipped FROM orders;
-- Oracle (and portable)
SELECT COUNT(CASE WHEN status='shipped' THEN 1 END) AS shipped FROM orders;
In practice, they’re usually the same (optimizers produce similar plans). Pick for clarity:
-- Both count matches
COUNT(CASE WHEN cond THEN 1 END)
SUM(CASE WHEN cond THEN 1 ELSE 0 END)
If you use SUM
, include ELSE 0
(or COALESCE
) to avoid NULL
results when no rows match.
Yes, multiple conditions with CASE WHEN can be counted using side-by-side columns or grouped buckets.
-- Parallel KPIs in one pass
SELECT
COUNT(CASE WHEN status='shipped' THEN 1 END) AS shipped,
COUNT(CASE WHEN status='pending' THEN 1 END) AS pending,
COUNT(CASE WHEN status='canceled' THEN 1 END) AS canceled
FROM orders;
-- Or bucket + GROUP BY
SELECT
CASE
WHEN amount > 1000 THEN 'high'
WHEN amount BETWEEN 501 AND 1000 THEN 'mid'
ELSE 'low'
END AS bucket,
COUNT(*) AS cnt
FROM orders
GROUP BY 1
ORDER BY 1;
COUNT
with CASE WHEN
is one of the most versatile SQL patterns for conditional counting, letting you calculate multiple KPIs in a single pass without extra queries. It works across all major databases, with dialect-specific shortcuts like PostgreSQL’s FILTER
, MySQL’s IF()
, SQL Server’s IIF()
, and Oracle’s DECODE()
.
For counting, you can use either COUNT(CASE WHEN) or SUM with CASE WHEN (Conditional Totals), with ELSE 0
ensuring no unexpected NULL
results. This approach scales easily to multiple categories, pairs well with GROUP BY
, HAVING
, and window functions, and is essential for dashboards, reporting, and interview-style problems.
Just remember to handle NULL
values explicitly, write non-overlapping conditions, index frequently filtered columns, and use partial or composite indexes when appropriate. Done right, COUNT CASE WHEN
delivers fast, clean, and accurate insights from complex datasets.
Ready to nail your next SQL interview? Start with our SQL Question Bank → to practice real-world scenario questions used in top interviews, questions. Sign up for Interview Query to test yourself with Mock Interviews → today.
Looking for hands-on problem-solving?
Test your skills with real-world challenges → from top companies to cement concepts before the interview.
Want role-specific SQL prep?
Browse tailored hubs like SQL for Data Analysts → and Data Scientist SQL Questions →. Pair with our AI Interview Tool → to sharpen your storytelling.
Need 1:1 guidance on your interview strategy?
Explore Interview Query’s Coaching Program → to work with a mentor, refine your prep, and build confidence.