apex-aggregate-queries

Use this skill when writing SOQL aggregate queries in Apex — GROUP BY, GROUP BY ROLLUP/CUBE, HAVING, COUNT/SUM/AVG/MIN/MAX, AggregateResult, and date grouping functions. Trigger keywords: soql aggregate groupby apex, count sum group by salesforce, aggregateresult get alias, having clause soql, rollup cube subtotals. NOT for relationship subqueries or inner queries (use apex-soql-relationship-queries), NOT for the Reporting API or Analytics Wave API.

Best use case

apex-aggregate-queries is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use this skill when writing SOQL aggregate queries in Apex — GROUP BY, GROUP BY ROLLUP/CUBE, HAVING, COUNT/SUM/AVG/MIN/MAX, AggregateResult, and date grouping functions. Trigger keywords: soql aggregate groupby apex, count sum group by salesforce, aggregateresult get alias, having clause soql, rollup cube subtotals. NOT for relationship subqueries or inner queries (use apex-soql-relationship-queries), NOT for the Reporting API or Analytics Wave API.

Teams using apex-aggregate-queries should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-aggregate-queries/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-aggregate-queries/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-aggregate-queries/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-aggregate-queries Compares

Feature / Agentapex-aggregate-queriesStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use this skill when writing SOQL aggregate queries in Apex — GROUP BY, GROUP BY ROLLUP/CUBE, HAVING, COUNT/SUM/AVG/MIN/MAX, AggregateResult, and date grouping functions. Trigger keywords: soql aggregate groupby apex, count sum group by salesforce, aggregateresult get alias, having clause soql, rollup cube subtotals. NOT for relationship subqueries or inner queries (use apex-soql-relationship-queries), NOT for the Reporting API or Analytics Wave API.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Apex Aggregate Queries

This skill activates when a practitioner needs to write SOQL aggregate queries inside Apex — covering GROUP BY, GROUP BY ROLLUP, GROUP BY CUBE, HAVING, COUNT/SUM/AVG/MIN/MAX, AggregateResult field access, date grouping functions, and the platform limits unique to aggregate queries.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm which object and fields will be aggregated. Numeric fields (Currency, Number, Percent) support SUM/AVG/MIN/MAX. Any field supports COUNT(fieldName).
- The most common wrong assumption is that aggregate queries share the flat-SOQL 50,000-row governor limit. They do not — aggregate queries cap at **2,000 rows** regardless of record volume.
- Verify whether GROUP BY ROLLUP or CUBE is needed; those variants generate subtotal rows that count toward the 2,000-row cap and require null-checking in Apex.

---

## Core Concepts

### AggregateResult and Field Access

SOQL aggregate queries return `List<AggregateResult>` instead of typed SObject lists. Every column in the result set — whether a grouped field or an aggregate function — must be accessed by alias string using `get('alias')`. There are no typed getters. The return type of `get()` is `Object`; cast to the expected type before use.

Aliases are assigned with the `alias` keyword in SOQL (e.g. `SUM(Amount) totalRevenue`). When an aggregate function appears without an explicit alias Salesforce auto-assigns names like `expr0`, `expr1` in declaration order. Relying on auto-assigned aliases is fragile — always supply explicit aliases. When the same function appears twice in one SELECT (e.g. `COUNT(Id)` on two different fields), explicit aliases are mandatory; without them one result overwrites the other.

### COUNT() vs COUNT(fieldName)

`COUNT()` (no argument) counts all rows that match the GROUP BY bucket, including rows where most fields are null. `COUNT(fieldName)` counts only rows where `fieldName` is non-null. The distinction matters when the counted field is optional or sparsely populated. `COUNT()` is equivalent to `COUNT_DISTINCT` only when used with no argument and no GROUP BY.

### GROUP BY Variants, HAVING, and the 2,000-Row Cap

`GROUP BY fieldA` groups result rows by distinct values of `fieldA`. `GROUP BY ROLLUP(fieldA, fieldB)` adds subtotal rows for each leading subset of the grouping columns plus a grand total row. `GROUP BY CUBE(fieldA, fieldB)` adds subtotals for every combination of grouping columns. Both ROLLUP and CUBE subtotal rows appear as `null` for the rolled-up dimension in the result — Apex code must null-check before casting.

The **2,000-row hard cap** applies to the entire result set including subtotal rows generated by ROLLUP/CUBE. If the expected cardinality of grouped results exceeds 2,000, split the query with WHERE filters or switch to Batch Apex.

`HAVING` filters on aggregate values after grouping (e.g. `HAVING SUM(Amount) > 10000`). It cannot be replaced by `WHERE` because `WHERE` runs before aggregation and cannot reference aggregate function output.

`QueryMore` (cursor-based pagination) is **not supported** for aggregate queries. The full result must fit within 2,000 rows.

### Date Grouping Functions

`CALENDAR_YEAR(dateField)`, `CALENDAR_MONTH(dateField)`, `DAY_IN_WEEK(dateField)`, `FISCAL_YEAR(dateField)`, `FISCAL_QUARTER(dateField)`, `HOUR_IN_DAY(dateTimeField)`, and related functions can appear in both `SELECT` and `GROUP BY`. They allow time-series grouping without post-processing in Apex. The return type from `get('alias')` for these functions is `Integer`. Always pair the function in SELECT with an alias.

### GROUP BY Incompatibilities

`GROUP BY` cannot be used inside inner queries (subqueries). It is also incompatible with `WITH DATA CATEGORY`. Attempting either causes a compile-time or query parse error.

---

## Common Patterns

### Revenue Rollup by Dimension with HAVING Threshold

**When to use:** Dashboard-style aggregation — e.g. total closed revenue per Account, filtered to accounts with revenue above a threshold.

**How it works:** Write a GROUP BY query with SUM and a HAVING clause. Cast `get('totalRevenue')` to Decimal. Null-check before arithmetic.

**Why not the alternative:** Fetching all Opportunity records and summing in Apex loops burns heap and CPU. The aggregate query shifts computation to the database tier.

### Time-Series Grouping with Date Functions

**When to use:** Monthly or annual trend analysis — e.g. opportunities closed per month over the past year.

**How it works:** Use `CALENDAR_YEAR(CloseDate)` and `CALENDAR_MONTH(CloseDate)` in both SELECT and GROUP BY. Cast the returned values to Integer in Apex. Sort by year and month in the SOQL ORDER BY clause.

**Why not the alternative:** String-formatting the date in Apex after a flat query requires loading all records and iterating in memory — this blows governor limits at scale.

### ROLLUP for Subtotals

**When to use:** Hierarchical reports — e.g. revenue grouped by Region and Product Family, with subtotals per Region and a grand total.

**How it works:** `GROUP BY ROLLUP(Region__c, Family)` produces N+1 grouping levels. Detect subtotal rows by checking `if (ar.get('region') == null)`. The grand total row has both grouping fields null.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Aggregate metric from a single object, result < 2,000 groups | Inline aggregate SOQL in Apex | Simplest; all processing in DB tier |
| Aggregate metric, expected groups > 2,000 | Batch Apex with WHERE range partitioning | Aggregate query row cap is 2,000 |
| Need subtotals without post-processing | GROUP BY ROLLUP or CUBE | Subtotals generated by DB; null sentinel marks subtotal rows |
| Filter on aggregate value (e.g. SUM > X) | HAVING clause | WHERE cannot reference aggregate output |
| Cross-object aggregate (e.g. parent field in GROUP BY) | Use dot-notation field in GROUP BY (e.g. Account.Industry) | Supported in aggregate SOQL; no join needed |
| Time-series grouping | CALENDAR_YEAR/CALENDAR_MONTH in SELECT and GROUP BY | DB-native; avoids heap-heavy Apex date parsing |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner working on this task:

1. **Define the metric and dimension** — identify the object, the numeric field to aggregate (or Id for COUNT), and the grouping dimension(s). Confirm whether the practitioner needs subtotals (ROLLUP/CUBE) or a simple GROUP BY.
2. **Estimate result cardinality** — if the number of distinct grouped values may exceed 2,000 (factoring in ROLLUP/CUBE subtotal rows), plan a partitioned approach (Batch Apex or WHERE-range splitting) before writing any query.
3. **Write the SOQL with explicit aliases** — assign an alias to every aggregate function. If the same function is used twice, aliases are required. Prefer descriptive aliases (`totalRevenue`, `oppCount`) over `expr0`.
4. **Add HAVING if filtering on aggregate output** — do not put aggregate-dependent filters in WHERE. Use HAVING for post-group filtering.
5. **Write the Apex iteration** — loop over `List<AggregateResult>`, call `ar.get('alias')` for each column, cast to the correct type (Decimal for SUM/AVG/MIN/MAX, Integer for COUNT and date functions), and null-check ROLLUP/CUBE subtotal rows.
6. **Validate governor limit exposure** — confirm the query is not inside a loop, and that the expected row count stays under 2,000. Add a unit test that mocks aggregate results.
7. **Run the query in Developer Console** — verify aliases match what Apex code expects; confirm HAVING filters produce the expected subset.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] Every aggregate function in SELECT has an explicit alias
- [ ] Apex code uses `ar.get('aliasName')` with cast — not a typed SObject getter
- [ ] Result row count (including ROLLUP/CUBE subtotals) is expected to stay under 2,000
- [ ] Filters on aggregate values use HAVING, not WHERE
- [ ] ROLLUP/CUBE subtotal rows are null-checked before casting
- [ ] No aggregate GROUP BY inside an inner/subquery
- [ ] No `WITH DATA CATEGORY` combined with GROUP BY
- [ ] Unit tests cover the aggregate iteration logic (mock or Test.setFixedSearchResults equivalent)

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **2,000-row cap (not 50,000)** — Aggregate queries are subject to a separate 2,000-row governor limit that applies org-wide, not the 50,000-row flat SOQL limit. GROUP BY ROLLUP and CUBE inflate the row count with subtotal rows that count against this same cap. Queries that look safe based on record count can silently fail at runtime when subtotal rows push the result over 2,000.
2. **Missing alias causes NullPointerException** — Calling `ar.get('myAlias')` when the query used no alias (or a typo alias) returns `null`; subsequent casts like `(Decimal) ar.get('myAlias')` throw a `NullPointerException`. Auto-assigned aliases (`expr0`, `expr1`) can shift silently when the SELECT column order changes. Always use explicit, stable aliases.
3. **ROLLUP row count inflation vs actual data** — `GROUP BY ROLLUP(A, B)` produces rows for each (A, B) pair PLUS one row per distinct A PLUS one grand total row. For N groups of A and M groups of B the row count is N×M + N + 1. Practitioners who only consider the base GROUP BY cardinality underestimate total rows and hit the 2,000-row cap unexpectedly.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| SOQL aggregate query | A well-formed query with explicit aliases, HAVING if needed, and correct date functions |
| Apex iteration snippet | Loop over `List<AggregateResult>` with typed casts and ROLLUP null guards |
| Cardinality estimate | Assessment of expected row count vs. the 2,000-row cap |

---

## Related Skills

- apex-soql-relationship-queries — use when GROUP BY needs to traverse a relationship (parent or child subquery) or when the query is part of a larger join-style pattern
- soql-fundamentals — foundational SOQL syntax, LIMIT/OFFSET, WHERE filters, and flat query governor limits
- batch-apex-patterns — use when aggregate result set may exceed 2,000 rows and requires partitioned execution
- apex-performance-profiling — use when aggregate queries are suspected as a CPU or SOQL-query-count bottleneck

Related Skills

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-apex-defined-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.

marketing-cloud-sql-queries

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing, debugging, or optimizing SQL Query Activities in Salesforce Marketing Cloud Automation Studio. Trigger keywords: SFMC SQL, Marketing Cloud query activity, system data views, _Sent _Open _Click, Automation Studio SQL, SELECT INTO data extension. NOT for SOQL (Salesforce CRM queries against sObjects), NOT for standard SQL databases, NOT for Data Cloud ANSI SQL, NOT for Query Studio as a standalone topic.

scheduled-apex-failure-detection-and-monitoring

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).

platform-events-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when publishing or subscribing to Salesforce Platform Events from Apex, comparing Platform Events with Change Data Capture, or designing event-triggered error handling and monitoring. Triggers: 'EventBus.publish', 'platform event trigger', 'CDC vs Platform Events', 'replay ID', 'high-volume event'. NOT for Flow-only publish/subscribe automation.

health-cloud-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Health Cloud via Apex: implementing HealthCloudGA managed-package interfaces, automating care plan lifecycle hooks, processing referrals using Industries Common Components invocable actions, or enforcing HIPAA-compliant logging governance for clinical Apex code. Trigger keywords: CarePlanProcessorCallback, HealthCloudGA namespace, ReferralRequest, ReferralResponse, care plan invocable actions, clinical Apex extension, Health Cloud Apex API. NOT for standard Apex triggers or generic Apex development unrelated to Health Cloud managed-package extension points.

fsl-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

fsc-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Financial Services Cloud (FSC) behavior through Apex: customizing financial rollup recalculation, disabling built-in FSC triggers to write custom trigger logic, implementing Compliant Data Sharing (CDS) participant/role integrations, or building custom FSC action handlers. NOT for standard Apex unrelated to the FSC managed package, standard Salesforce sharing rules, or configuring FSC rollups through the Admin UI — use the admin/financial-account-setup skill for declarative rollup setup.

entitlement-apex-hooks

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing Apex triggers or classes that interact with CaseMilestone records — completing milestones, detecting violations, or reacting to SLA state changes. Trigger keywords: CaseMilestone trigger, auto-complete milestone Apex, milestone violation polling, CompletionDate write pattern. NOT for entitlement process admin setup, milestone configuration in Setup UI, or Flow-based milestone actions.