The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Conditional aggregation applies a different predicate inside each aggregate, allowing one grouped query to calculate several metrics from the same rows. The portable form is SUM(CASE WHEN ... THEN ... ELSE ... END); databases that support it can use the shorter FILTER (WHERE ...) clause.
SELECT
customer_id,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_orders,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue
FROM orders
GROUP BY customer_id;
What conditional aggregation solves
GROUP BY creates one reporting row per group. Each aggregate then evaluates its own condition, so the same input rows can produce several measures without separate queries.
| order_id | customer_id | status | amount |
|---|---|---|---|
| 1 | 101 | paid | 120 |
| 2 | 101 | pending | 80 |
| 3 | 101 | cancelled | 40 |
| 4 | 102 | paid | 200 |
| 5 | 102 | paid | 50 |
SELECT
customer_id,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_orders,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue
FROM orders
GROUP BY customer_id;
| customer_id | paid_orders | pending_orders | paid_revenue |
|---|---|---|---|
| 101 | 1 | 1 | 120 |
| 102 | 2 | 0 | 250 |
Running separate queries would require separate scans or result-combining logic. A top-level WHERE status = 'paid' would remove pending and cancelled rows before the other aggregates could see them.
The core patterns
Count matching rows with SUM(CASE)
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_count
Every matching row contributes 1 and every other row contributes 0. This is explicit and broadly portable.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
Count matching rows with COUNT(CASE)
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_count
COUNT(expression) counts non-NULL expression results. A matching branch returns 1; with no ELSE, a nonmatching branch returns NULL. Use a guaranteed non-NULL marker such as 1, not a nullable business column.
-- Risky when customer_id can be NULL
COUNT(CASE WHEN status = 'paid' THEN customer_id END)
-- Safer
COUNT(CASE WHEN status = 'paid' THEN 1 END)
Use COUNT(*) for an unconditional row count.
Conditional sums
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue
If amount can be NULL, a matching row may contribute NULL. Normalize it only when a missing amount should mean zero:
SUM(CASE
WHEN status = 'paid' THEN COALESCE(amount, 0)
ELSE 0
END) AS paid_revenue
Conditional minimum and maximum
MAX(CASE WHEN status = 'paid' THEN amount END) AS largest_paid_order,
MIN(CASE WHEN status = 'paid' THEN order_date END) AS first_paid_order_date
Leaving unmatched rows as NULL prevents an artificial zero, empty string, or date from becoming the result.
Conditional averages
AVG(CASE WHEN status = 'paid' THEN amount END) AS average_paid_order
Do not normally write ELSE 0 here. Zero-valued nonmatching rows would enter the denominator and lower the average. Most built-in aggregates ignore NULL inputs, although exact behavior depends on the function and engine; PostgreSQL documents this qualification at its aggregate-function reference.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteELSE 0, implicit NULL, and empty results
For count-like sums, use ELSE 0 when you want a group with no matches to produce a numeric zero. An omitted ELSE returns NULL for nonmatches, and SUM can itself return NULL when it receives no non-NULL values.
COALESCE(
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END),
0
) AS paid_count
For sums, COALESCE expresses a business decision:
- No matching rows.
- Matching rows whose measures are all
NULL. - Matching rows whose true total is zero.
Those states may or may not be equivalent. Apply COALESCE only when they should be reported identically.
Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
WHERE, conditional aggregates, and HAVING
WHERE filters every aggregate input
SELECT customer_id, COUNT(*) AS paid_orders
FROM orders
WHERE status = 'paid'
GROUP BY customer_id;
This is appropriate when every metric concerns paid orders, but it cannot also calculate pending orders from the same query block.
Conditions inside aggregates filter one metric
SELECT
customer_id,
COUNT(*) AS all_orders,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_orders
FROM orders
GROUP BY customer_id;
HAVING filters completed groups
SELECT
customer_id,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue
FROM orders
GROUP BY customer_id
HAVING SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) > 500;
WHERE restricts rows before grouping; HAVING removes groups after aggregation. PostgreSQL describes this distinction in its table-expression documentation and notes that nonaggregate row restrictions generally belong in WHERE.
Combining all three
SELECT
customer_id,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue,
SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END) AS refunded_amount
FROM orders
WHERE order_date >= DATE '2026-01-01'
GROUP BY customer_id
HAVING COUNT(*) >= 5;
The date limits the reporting period, each aggregate separates a metric, and HAVING keeps customers with at least five period rows.
The FILTER (WHERE ...) alternative
PostgreSQL and DuckDB support a per-aggregate filter:
SELECT
customer_id,
COUNT(*) AS all_orders,
COUNT(*) FILTER (WHERE status = 'paid') AS paid_orders,
SUM(amount) FILTER (WHERE status = 'paid') AS paid_revenue
FROM orders
GROUP BY customer_id;
PostgreSQL documents the syntax in aggregate expressions and its aggregate tutorial. DuckDB documents localized filtering, pivot use, and cleaner handling for collection aggregates at its FILTER reference. Do not assume that every database accepts this syntax.
| Pattern | Strength | Limitation |
|---|---|---|
SUM(CASE ...) |
Familiar and broadly portable | More verbose; NULL choices are explicit |
COUNT(CASE ...) |
Direct conditional count | Easy to count a nullable expression accidentally |
COUNT(*) FILTER (WHERE ...) |
Concise and semantic | Support varies by engine |
SUM(...) FILTER (WHERE ...) |
Clean conditional sum or collection input | Check dialect compatibility |
Several conditions, thresholds, and buckets
SELECT
region,
COUNT(*) AS total_orders,
SUM(CASE WHEN amount >= 1000 THEN 1 ELSE 0 END) AS large_orders,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders,
SUM(CASE
WHEN status = 'paid' AND amount >= 1000
THEN amount
ELSE 0
END) AS large_paid_revenue
FROM orders
GROUP BY region;
Each expression is independent. A row can contribute to several metrics when predicates overlap. That is correct for threshold measures such as “at least 100” and “at least 500.”
Recommended Free Tools
Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
For mutually exclusive buckets, make both boundaries explicit:
SUM(CASE WHEN amount < 100 THEN 1 ELSE 0 END) AS under_100,
SUM(CASE WHEN amount >= 100 AND amount < 500 THEN 1 ELSE 0 END) AS from_100_to_499,
SUM(CASE WHEN amount >= 500 THEN 1 ELSE 0 END) AS 500_or_more
Do not define adjacent groups as <= 100 and >= 100 unless overlap is intentional. For exhaustive buckets, reconcile their sum with the total count.
Rates, percentages, and denominators
SELECT
region,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) * 1.0
/ NULLIF(COUNT(*), 0) AS paid_rate
FROM orders
GROUP BY region;
The decimal literal (or an explicit cast) avoids integer division where integer operands would truncate. NULLIF prevents division by zero. Multiply by 100 only when the result should be a percentage.
100.0 * COUNT(CASE WHEN status = 'paid' THEN 1 END)
/ NULLIF(COUNT(*), 0) AS paid_percentage
Define the denominator before writing the expression: paid orders divided by all orders is not the same as paid revenue divided by all revenue. A group-level weighted rate is also different from averaging row-level percentages.
Free tools Windows power users keep installed
One-click scans. No signup required.
Conditional DISTINCT counts
SELECT
campaign_id,
COUNT(DISTINCT CASE WHEN converted = 1 THEN user_id END) AS converted_users
FROM events
GROUP BY campaign_id;
Where supported, the equivalent is:
COUNT(DISTINCT user_id) FILTER (WHERE converted = 1) AS converted_users
Event tables often contain multiple rows per user. Count distinct users when the metric is users, rather than counting events. Conversely, SUM(DISTINCT amount) removes duplicate numeric values, not duplicate orders: two different orders worth 100 would be counted once. Pre-aggregate at the required entity grain instead.
Joins, grain, and inflated metrics
Always identify the grain of the result—row, order, customer, session, or another entity. Joining two independent one-to-many tables before aggregation can create a customer × orders × payments row set and multiply every count and sum.
Rank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
-- Potentially inflated: both child tables are one-to-many
SELECT
c.customer_id,
COUNT(CASE WHEN o.status = 'paid' THEN 1 END) AS paid_orders,
SUM(p.amount) AS payments
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
LEFT JOIN payments p ON p.customer_id = c.customer_id
GROUP BY c.customer_id;
Aggregate each relationship first, then join the one-row-per-customer results:
WITH order_metrics AS (
SELECT
customer_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders
FROM orders
GROUP BY customer_id
),
payment_metrics AS (
SELECT customer_id, SUM(amount) AS total_payments
FROM payments
GROUP BY customer_id
)
SELECT
c.customer_id,
COALESCE(o.total_orders, 0) AS total_orders,
COALESCE(o.paid_orders, 0) AS paid_orders,
COALESCE(p.total_payments, 0) AS total_payments
FROM customers c
LEFT JOIN order_metrics o ON o.customer_id = c.customer_id
LEFT JOIN payment_metrics p ON p.customer_id = c.customer_id;
COUNT(DISTINCT ...) can correct one entity count, but it does not generally repair inflated sums or an incorrect join grain.
NULL and three-valued logic
Unknown conditions
A comparison with NULL normally evaluates to UNKNOWN, not true. Handle a null status explicitly when it has business meaning:
SUM(CASE WHEN status IS NULL THEN 1 ELSE 0 END) AS missing_status
Nullable measures
In SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END), a matching null amount contributes null. If all matching amounts are null, the aggregate may be null. Replacing it with COALESCE(amount, 0) changes the metric and should be deliberate.
Outer joins
A parent retained by a LEFT JOIN has null child columns when no child exists. Decide whether that parent should receive zero, null, or an explicit “no records” state.
Dates and timestamps
Use half-open intervals: include the start and exclude the next boundary.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
SUM(CASE
WHEN created_at >= TIMESTAMP '2026-01-01 00:00:00'
AND created_at < TIMESTAMP '2026-02-01 00:00:00'
THEN 1
ELSE 0
END) AS january_rows
For date columns:
SUM(CASE
WHEN order_date >= DATE '2026-01-01'
AND order_date < DATE '2026-02-01'
THEN amount
ELSE 0
END) AS january_revenue
This avoids losing fractional-second values with an inclusive 23:59:59 endpoint. Date literals, timestamp types, time-zone conversion, session time zones, and date-truncation functions differ by product; account for daylight-saving transitions and whether the column is a date, a timestamp without time zone, or a timestamp with time zone.
Grouping by time and other dimensions
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue,
SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END) AS refunded_amount
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
DATE_TRUNC is not portable syntax. Every nonaggregate selected expression generally must be grouped, while aggregate predicates do not replace the GROUP BY. Without GROUP BY, aggregate queries form one overall group; PostgreSQL documents these rules at queries and table expressions.
Grouped aggregates versus window aggregates
Grouped aggregation collapses rows:
SELECT
department,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_count
FROM employees
GROUP BY department;
A window aggregate preserves each employee while displaying the department metric:
SELECT
employee_id,
department,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END)
OVER (PARTITION BY department) AS active_count_in_department
FROM employees;
Choose the grouped form for one row per group and the window form when detail rows and group context must coexist. Window support and clause placement still vary by engine.
Conditional aggregation as a manual pivot
SELECT
region,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid,
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS pending,
SUM(CASE WHEN status = 'cancelled' THEN amount ELSE 0 END) AS cancelled
FROM orders
GROUP BY region;
This is explicit, portable, and suitable when categories are known. It becomes unwieldy when categories are numerous or dynamic; native PIVOT, generated SQL, or a reporting layer may then be more appropriate. DuckDB specifically presents FILTER as useful for pivoted views and host-language generation.
Dialect notes
| Feature | Portable baseline | PostgreSQL | DuckDB | Snowflake | BigQuery |
|---|---|---|---|---|---|
CASE inside an aggregate |
Primary pattern | Supported | Supported | Supported | Supported |
FILTER (WHERE ...) |
Verify per engine | Documented | Documented | Verify current support | Verify current support |
| Date functions and typed literals | Dialect-specific | Dialect-specific | Dialect-specific | Dialect-specific | Dialect-specific |
| Integer division | Verify behavior | Verify behavior | Verify behavior | Verify behavior | Verify behavior |
| Native pivot | Vendor-specific | Vendor-specific | Vendor-specific | Vendor-specific | Vendor-specific |
Snowflake documents conditional expressions such as CASE, IFF, IFNULL, NULLIF, and COALESCE at its conditional-expression reference. BigQuery documents aggregate-call modifiers at its aggregate-function reference; check the specific function before transferring syntax. Vendor helper functions are not automatically portable.
Failure-proofing checklist
- What is the input row grain and the desired output grain?
- Does each condition overlap another condition intentionally?
- Should nonmatches contribute zero or remain null?
- Is the expression counted guaranteed to be non-null?
- Could joins multiply rows?
- Is the denominator the correct population for the rate?
- Could integer division truncate the result?
- Are timestamp boundaries half-open and time-zone aware?
- Does the target engine support
FILTER, date syntax, and the chosen pivot or window syntax? - Have results been reconciled with a smaller independently filtered query?
For exhaustive mutually exclusive buckets, verify that the bucket total equals COUNT(*). For risky expressions, do not assume a surrounding CASE controls aggregate evaluation order; PostgreSQL documents that aggregate expressions can be evaluated before other select-list or HAVING expressions at its expression reference. Use a separate query level or a safe expression instead.
Quick Recap
Quick reference
-- Conditional count
SUM(CASE WHEN condition THEN 1 ELSE 0 END)
-- Conditional count with COUNT
COUNT(CASE WHEN condition THEN 1 END)
-- Conditional sum
SUM(CASE WHEN condition THEN amount ELSE 0 END)
-- Conditional average
AVG(CASE WHEN condition THEN amount END)
-- Conditional distinct entities
COUNT(DISTINCT CASE WHEN condition THEN entity_id END)
-- Conditional rate
SUM(CASE WHEN condition THEN 1 ELSE 0 END) * 1.0
/ NULLIF(COUNT(*), 0)
-- PostgreSQL/DuckDB alternative
COUNT(*) FILTER (WHERE condition)
SUM(amount) FILTER (WHERE condition)
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

