In Salesforce Batch Apex, the start() method uses a Database.QueryLocator to retrieve records for processing. Internally, Salesforce uses the queryMore() method to paginate through large result sets returned by the QueryLocator.
However, aggregate SOQL queries — those that use clauses such as GROUP BY, MIN(), MAX(), COUNT(), or SUM() — do not support queryMore(). Attempting to use an aggregate query directly in a Batch Apex start() method causes the following error:
"Aggregate query does not support queryMore(), use LIMIT to restrict the results to a single batch."
This article describes two supported patterns for working around this limitation.
This approach separates the aggregate query from the Batch class. A Schedulable class runs the aggregate SOQL query using the @ReadOnly annotation, which bypasses standard governor limits on query rows and allows large result sets to be queried.
The aggregate result is passed as a parameter to the Batch class constructor. The Batch class then uses a standard non-aggregate SOQL query in its start() method, filtered by the value obtained from the aggregate query.
For example, a Schedulable class annotated with @ReadOnly runs a query such as SELECT MIN(CreatedDate) CD FROM Account using aggregate syntax. It extracts the result value from the AggregateResult list and passes it to the Batch class constructor. The Batch class stores this value as an instance variable and uses it in a standard SOQL query in start(), such as SELECT Id FROM Account WHERE CreatedDate = :dt.
Note: The code patterns below are illustrative examples. Modify and test in a sandbox before deploying to production.
Schedulable Class: Implement the Schedulable interface and annotate the execute() method with @ReadOnly. Run the aggregate query, extract the result value, and call Database.executeBatch() passing the result to the Batch class constructor.
Batch Class: Implement Database.Batchable. Accept the aggregate result value in the constructor. In start(), use Database.getQueryLocator() with a standard SOQL query filtered by the value received from the Schedulable class.
This approach replaces the Database.QueryLocator return type in start() with a custom Iterable implementation. The Batch class implements Database.Batchable and returns a custom Iterable from start(). The custom Iterable class implements Iterable and returns a custom Iterator. The Iterator class runs the aggregate SOQL query directly, stores the results in a list, and implements hasNext() and next() methods to iterate over the AggregateResult records.
Iterable Class: Implements Iterable. The Iterator() method returns an instance of the custom Iterator class.
Iterator Class: Implements Iterator. The constructor runs the aggregate SOQL query (e.g., SELECT Id, MIN(CreatedDate) FROM Account GROUP BY Id LIMIT 1) and stores the results. The hasNext() method checks whether more results remain, and next() returns the next AggregateResult.
Batch Class: Implements Database.Batchable. The start() method returns a new instance of the Iterable class. The execute() method processes each AggregateResult in the batch scope.
000386516

We use three kinds of cookies on our websites: required, functional, and advertising. You can choose whether functional and advertising cookies apply. Click on the different cookie categories to find out more about each category and to change the default settings.
Privacy Statement
Required cookies are necessary for basic website functionality. Some examples include: session cookies needed to transmit the website, authentication cookies, and security cookies.
Functional cookies enhance functions, performance, and services on the website. Some examples include: cookies used to analyze site traffic, cookies used for market research, and cookies used to display advertising that is not directed to a particular individual.
Advertising cookies track activity across websites in order to understand a viewer’s interests, and direct them specific marketing. Some examples include: cookies used for remarketing, or interest-based advertising.