Home / apex / soql-aggregate-field-type-support
soql-aggregate-field-type-support
Use when you need to know which of the six SOQL aggregate functions (AVG, COUNT, COUNT_DISTINCT, MIN, MAX, SUM) a given Salesforce field type supports — e.g.
Open the skill folder on GitHub
SOQL Aggregate Field-Type Support
This skill activates when a practitioner needs to know which SOQL aggregate functions apply to which field types. Salesforce publishes a dedicated reference page for exactly this because the six functions "aren't relevant for all field types" — SUM() and AVG() are numeric-only, several field types support no aggregate at all, and a few (currency, picklist) carry non-obvious semantics. The payoff is catching a SUM(CloseDate)-style mistake at authoring time instead of as a runtime query error.
Before Starting
Gather this context before working on anything in this domain:
- Know the exact data type of every field you plan to aggregate. "It's a number on screen" is not enough — a currency, a percent, a formula returning text, and a picklist all behave differently. Read the type from Setup → Object Manager or a describe call, not from the label.
- The most common wrong assumption is that any field can be summed or averaged.
AVG()andSUM()require a fully numeric type (int, double, currency, percent). On a date, text, picklist, or boolean field the query fails — it does not silently return null. - The six functions are AVG, COUNT, COUNT_DISTINCT, MIN, MAX, SUM.
MAX()has been available since API version 18.0 and later. This is longstanding core SOQL reference behavior; the docs attach no GA/Beta/Pilot/Developer-Preview label to it — do not invent one. - Multi-currency changes the answer. In a multi-currency org, aggregate results on currency fields default to the system (corporate) currency, not each record's currency.
Core Concepts
The six functions and what each returns
| Function | Returns | Field-type requirement |
|---|---|---|
AVG(field) | Average value of a numeric field | Numeric only |
SUM(field) | Total sum of a numeric field | Numeric only |
COUNT() / COUNT(fieldName) | Number of rows matching the query | Broad (see matrix) |
COUNT_DISTINCT(field) | Number of distinct non-null values | Broad (see matrix) |
MIN(field) | Minimum value of a field | Broad (see matrix) |
MAX(field) | Maximum value of a field (API 18.0+) | Broad (see matrix) |
The compatibility matrix (the reference this skill exists for)
| Field type(s) | AVG | SUM | COUNT | COUNT_DISTINCT | MIN | MAX |
|---|---|---|---|---|---|---|
| Numeric — int, double, currency, percent | Yes | Yes | Yes | Yes | Yes | Yes |
| Date/time — date, dateTime | No | No | Yes | Yes | Yes | Yes |
| Text-like — reference/lookup, ID, email, phone, url, textarea, picklist, combobox, DataCategoryGroupReference | No | No | Yes | Yes | Yes | Yes |
| No aggregate support — base64, boolean, time, multipicklist, address, location, encryptedstring | No | No | No | No | No | No |
| Calculated (formula) | Depends on the formula's return type — apply the row for that type |
Two behaviors do not fit in a cell:
base64is excluded because aggregating it "wouldn't generate any meaningful data." The exclusion is deliberate, not a bug.MIN()/MAX()on a picklist uses the picklist's defined value sort order, not alphabetical order. SoMIN(Stage)returns the first stage as ordered in Setup, which may not be the alphabetically-first label.
Null handling — COUNT() is the exception
All aggregate functions ignore null values except COUNT() and COUNT(Id). Practical consequence:
COUNT()andCOUNT(Id)count every matching row (nulls included).COUNT(fieldName)andCOUNT_DISTINCT(fieldName)count only rows where that field is populated (nulls skipped).
So COUNT(Id) vs COUNT(Email) on the same result set can legitimately differ — the gap is the number of records with a blank Email.
Formula fields resolve by return type
A calculated (formula) field has no fixed rule: "Support for aggregate functions depends on the type of the calculated field." A formula returning Currency behaves like currency (all six); a formula returning Text behaves like text (no AVG/SUM). Determine the formula's return type first, then apply the matching matrix row.
Common Patterns
Earliest / latest instead of "average date"
When to use: you need a date summary across a group but reached for AVG()/SUM() out of SQL habit.
How it works: date and dateTime support MIN() and MAX() (and the counts), so use MIN(CreatedDate) for the earliest and MAX(CreatedDate) for the latest. There is no average or sum of dates — the query will error if you try.
Why not the alternative: AVG(CreatedDate) is not "unsupported but harmless" — it fails the query outright, breaking the whole request, not just that column.
Multi-currency aggregate on a currency field
When to use: you SUM() or AVG() an Amount-style currency field in a multi-currency org.
How it works: the aggregate result defaults to the corporate (system) currency, so a raw SUM(Amount) blends converted values into one number. To keep each subtotal in a single, unambiguous currency, GROUP BY CurrencyIsoCode:
SELECT CurrencyIsoCode, SUM(Amount) total
FROM Opportunity
GROUP BY CurrencyIsoCode
Why not the alternative: an ungrouped SUM(Amount) in a mixed-currency org produces a corporate-currency figure that few stakeholders expect and none can trace back to a currency; the grouped form is auditable.
Count all rows vs count populated values
When to use: you're reporting "how many" and need to be precise about nulls.
How it works: use COUNT(Id) (or COUNT()) for the total record count; use COUNT(field) or COUNT_DISTINCT(field) when you specifically want the count of records where field is populated (or the count of unique values). Keep the SOQL inside a selector method — see