You need to sign in to do that
Don't have an account?
Steve Berley [Left Propeller]
Iterable Batch giving error -- Too many query rows: 50001
I'm running into the dreaded too many query rows error on an iterable batch. No matter the batch size I use, it immediately errors out; even if I set the batch size to 1. Looking at the logs, it seems to happen while still in the start() method.
This is driving me nuts - I look forward to your insights...
Thanks!
Here's the batch...
The Iterable
The iterator
This is driving me nuts - I look forward to your insights...
Thanks!
Here's the batch...
global with sharing class update_MBI implements Database.Batchable<AggregateResult> { global update_MBI() { } global string mbiQuery = 'select account__c, sum(spend__c) spendLast12 from spend__c where age__c < 0 and age__c >= -12 group by account__c '; global Iterable<AggregateResult> start(Database.BatchableContext BC) { return new mbiIterable(mbiQuery); } global void execute(Database.BatchableContext BC, List<AggregateResult> scope) { list<account> u = new list<account>(); for (AggregateResult ar : scope){ id acctID = id.valueOf(string.valueOf(ar.get('account__c'))); decimal newMBI = decimal.valueOf(string.valueOf(ar.get('spendLast12'))) / 12; u.add(new account(id=acctID, MBI_Monthly_Spend_Current__c = newMBI)); } update u; } global void finish(Database.BatchableContext BC) { } }
The Iterable
public with sharing class mbiIterable implements Iterable<AggregateResult> { private String query; public mbiIterable(String soql){ query = soql; } public Iterator<AggregateResult> Iterator(){ return new mbiIterator(query); } }
The iterator
public class mbiIterator implements Iterator<AggregateResult> { AggregateResult[] results { get;set; } Integer index { get;set; } public mbiIterator(String query) { index = 0; results = Database.query(query); } public Boolean hasNext(){ return results != null && !results.isEmpty() && index < results.size(); } public AggregateResult next() { return results[index++]; } }
Add limit 50000 in your query like below
select account__c, sum(spend__c) spendLast12 from spend__c where age__c < 0 and age__c >= -12 group by account__c limit 50000
Query Editor (Developer console): select count() from spend__c where age__c < 0 and age__c >= -12
Result: > 50.000 ?
global string mbiQuery = 'select account__c, sum(spend__c) spendLast12 from spend__c where age__c < 0 and age__c >= -12 group by account__c ';
It is one of the most-dread limitation of Salesforce in Apex.
You cannot process more than 50.000 records in Apex (including for counting with id or grouping them) with one call of a SOQL query but that works by using a query editor and Rest queries (if the timeout limit is not reached).
Query editor (Developer console): select account__c, sum(spend__c) from spend__c where age__c < 0 and age__c >= -12 group by account__c Does that work? (rest query)?
This limitation in Apex has this workaround
You summarize the global result by chuncks of 10.000 records.
for(AggregateResult result :[SELECT COUNT(Id) intVal FROM Custom_Object__c]) : has an implicit limitation of 10.000 records.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm
Idea: Count the SOQL count() query as a single row query
https://success.salesforce.com/ideaView?id=08730000000Br7TAAS
I'm surprised you find that this works because for loops page includes the following passage
Note that queries including an aggregate function don't support queryMore. A run-time exception occurs if you use a query containing an aggregate function that returns more than 2,000 rows in a for loop
So I'm not sure how that would work using the average function.
However, since I'm averaging by account - my latest thinking is to break batch to do accounts with names starting with A-L then M-Z. I would have the finish method of an A-L dispatch the batch for M-Z. Yeah, it's a bit hacky, but it should work.
The maximum number of records for the result is 2,000 rows but the read records is also limited in Apex to 50,000 records in APEX.
I don't know your data;
Query Editor (Developer console): select count() from spend__c where age__c < 0 and age__c >= -12 ( WITHOUT GROUPING only COUNT() = read records )
Result: > 50.000 ? ( I don't know, only you can give us the result)
I feel that you confuse the result of the aggregation and the read records to get this result (and the query in Rest or in Apex)/
I'm in the unfortunate position of both an excess of 50k records to read and more than 2000 records in the aggregated result. So, I need to work my way out of two holes.
Since I'm aggregating within accounts the idea of managing scope using the first letter of account names becomes workable, though not terribly appealing.
I finally created a test with a scratch org ( 200 mo instead 5 mo for a developer org ) with more than 100.000 rows.
You can query large object with the query editor of the developer console directly but that will not work with Apex code:
But the simple Apex code below generates the Too many query rows: 50001
I tested this code:
I often use the tested results of questions in this forum for my own work but all the questions are not interesting.
I take advantage of the fact I'm aggregating within accounts so limit each run to accounts beginning with a single letter. For example all accounts starting with A,
The genQuery() method dynamically generates the soql query and I cycle through the alphabet with each batch kicking off the next one in the finish() method.
Yeah, it's inelegant, but it gets the job done and I won't face 50k+ accounts starting witih a given letter anytime soon.
Thanks for asking and Happy Holidays...
Steve
All the "tricks" are valuable and we get the results surely (that is the most important).
The inner loop above will block at 2,000 accounts differents by bunch of 50,000 (that is always possible) so that is not perfect either (that is a partial workaround sufficent in some cases).
This most-dread limitation of Salesforce should have a "best practice/solution" provided by the engineering of Salesforce directly because it is a fundamental need but I didn't find it.
Wait and see if someone can provide a more "elegant" solution but that will be difficult according to me.
You need to sample the data with sub-grouping values indeed (trusting that the governor limits will be never reached either with a sufficient margin).
Happy Holidays.
Alain
I was worried about the 2000 record limit in the AggregateResult. Fortunately my worst letter has barely 500 in the AR set so it looks like I'll be set until they find a better solution to the problem.
Steve
If you want to be "sure" (wide margin 500 / 2000 ), the subgrouping (your solution) is surer (verified wider margin).