Aggregates, GROUP BY & Date Literals
COUNT, SUM, GROUP BY, HAVING, AggregateResult in Apex and the date literals that keep queries readable.
Developer 2 min readSOQLGROUP BYHAVINGAggregatesDate literals
Aggregate functions
| Function | Notes |
|---|---|
COUNT() | Returns a single Integer (totalSize), no rows |
COUNT(field) | Counts non-null values |
COUNT_DISTINCT(field) | Distinct non-null values |
SUM, AVG, MIN, MAX | Numeric (MIN/MAX also dates, strings) |
soql
SELECT COUNT() FROM Case WHERE Status != 'Closed'
apex
Integer openCases = [SELECT COUNT() FROM Case WHERE Status != 'Closed'];
GROUP BY and HAVING
WHERE filters rows before grouping, HAVING filters groups after aggregation.
soql
SELECT Industry, SUM(AnnualRevenue) revenue, COUNT(Id) accounts
FROM Account
WHERE Industry != null
GROUP BY Industry
HAVING SUM(AnnualRevenue) > 5000000
ORDER BY SUM(AnnualRevenue) DESC
LIMIT 3
Rules:
- Every non-aggregated field in
SELECTmust be inGROUP BY→ otherwise "Field must be grouped or aggregated". - Aliases (
revenue) only work on aggregate expressions; unaliased ones are namedexpr0,expr1, … - You can
ORDER BYan aggregate.
AggregateResult in Apex
apex
Map<Id, Integer> contactsPerAccount = new Map<Id, Integer>();
for (AggregateResult ar : [
SELECT AccountId acc, COUNT(Id) n
FROM Contact
WHERE AccountId IN :accountIds
GROUP BY AccountId
]) {
contactsPerAccount.put((Id) ar.get('acc'), (Integer) ar.get('n'));
}
This is the standard roll-up on a lookup pattern used in triggers (a roll-up summary field only works on master-detail).
Date literals
Relative to the running user's time zone — no hard-coded dates.
| Literal | Meaning |
|---|---|
TODAY, YESTERDAY, TOMORROW | One day |
THIS_WEEK, LAST_WEEK, NEXT_WEEK | Calendar week |
THIS_MONTH, LAST_MONTH, NEXT_MONTH | Calendar month |
THIS_QUARTER, THIS_FISCAL_QUARTER | Calendar / fiscal quarter |
LAST_N_DAYS:30, NEXT_N_DAYS:7, N_DAYS_AGO:3 | Rolling ranges |
soql
SELECT Name, Amount, CloseDate
FROM Opportunity
WHERE IsClosed = false
AND Amount >= 50000
AND CloseDate = THIS_QUARTER
ORDER BY CloseDate, Name
Date vs Datetime literals: CloseDate = 2026-06-30 (Date field) vs CreatedDate > 2026-06-30T00:00:00Z (Datetime field).
Interview questions
- Difference between
WHEREandHAVING? → Row filter vs group filter. - How do you count child records per parent without a roll-up summary? → Aggregate SOQL grouped by the lookup field, then update parents.
- What does
COUNT()return vsCOUNT(Id)? →COUNT()returns an Integer total;COUNT(Id)returns AggregateResult rows (usable with GROUP BY).