function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
KajaKaja 

System.Exception: Too many SOQL queries: 21

Hi,
 
I am new to Apex trigger and need to get rid of the following error while doing bulk updates on Opportunity.
Update Row Failed:
tocOpportunityAfterUpdate: execution of AfterUpdate
caused by: System.Exception: Too many SOQL queries: 21
Trigger.tocOpportunityAfterUpdate: line 8, column 42
The following is the trigger which needs modification to avoid the above bulk update error.
Code:
trigger tocOportunityAfterUpdate on Opportunity (after update) {
    integer i=0;
    
    try {
        for (Opportunity o : Trigger.new) {
            String id = Trigger.new[i].Id;
            if (Trigger.new[i].Billing_Report_Date__c != null && (Trigger.old[i].Billing_Report_Date__c == null || Trigger.old[i].Billing_Report_Date__c != Trigger.new[i].Billing_Report_Date__c)) {
                Opportunity[] oppList = [Select o.tocClonedFrom__c, o.RecordType.Name, o.Id, o.Billing_Report_Date__c, (Select Id, Billing_Report_Date__c From Opportunities__r) From Opportunity o where (o.tocClonedFrom__c=:id) or o.Id = :id];
    
                for (Opportunity op : oppList) {
                    if(op.RecordType.Name == 'Payment' && op.Id != id ) 
                    {
                        Opportunity updOpp = new Opportunity(Id=op.Id);
                        updOpp.Billing_Report_Date__c  = Trigger.new[i].Billing_Report_Date__c;   
                        update updOpp;
                    }
                    if((op.tocClonedFrom__c == id && op.RecordType.Name == 'Payment') || op.Id == id )
                    {
                        tfCommissionOpportunity__c[] commOppsList = op.Opportunities__r;
                            for(tfCommissionOpportunity__c commOpp : commOppsList){
                            tfCommissionOpportunity__c updCommOpp = new tfCommissionOpportunity__c(Id=commOpp.Id);
                            updCommOpp.Billing_Report_Date__c = Trigger.new[i].Billing_Report_Date__c;
                            update updCommOpp;
                        }
                    }
                } 
            }
            i += 1;

        }
    } catch (Exception e){
        
    }
}

I've looked in the apex documentation for bulk triggers. It's bad to put soql statements in a for loop. the batch size for each trigger can be from 1 to 200. if the batch size is high, having a soql statement in the for loop will give you the soql error.
 
It would be very useful for me, if you people modified the above code to get rid of that error.
 
thanks
venkat
TLFTLF
You could try collecting your Opportunity objects that require updating in a List<Opportunity> and then doing a single update at the end of your for loop, passing it the List<Opportunity> object. This works for batches of 200 or less. If your collection size is greater than 200, then you will have to implement code to process the collection in chunks of 200 objects or less.
 
I don't think the batch updates are your only problem however, the error is indicating you have executed too many SOQL queries. Your select statement is inside your for loop too, and that is executing more than 20 times (the limit on SOQL queries within Apex triggers). To remedy that, you might consider using an IN clause to compare multiple IDs at once, rather than using = in your SOQL WHERE clause.
jrotensteinjrotenstein
Agreed. This needs to be redesigned to minimize the number of DML calls (select, update, insert). Optimally, you should have a maximum of one select and one update per object type.

Also, you could consider using oldMap and newMap to retrieve Opportunities passed into the Trigger, rather than having to iterate through them (eg use Trigger.oldMap.get(id) instead of i += 1 stuff).

Have a look through samples of Triggers on these forums for ideas.
KajaKaja
Thanks a lot for the tips Sir. :-)
 
I've sligtly modified the code according to your suggestions with comments
 
Code:
trigger tocOportunityAfterUpdate on Opportunity (after update) {
    List<Opportunity> allids = new List<Opportunity>();
    List<Opportunity> toUpdate = new List<Opportunity>();
    List<tfCommissionOpportunity__c> toComUpdate = new List<tfCommissionOpportunity__c>();
    try {

        /*This for loop was redesigned to minimize the number of DML calls(select)
        Collecting the Opportunity objects that require updating in a List<Opportunity> and then doing a single update at the end of the for loop, 
        passing it the List<Opportunity> object.        */

        for (Opportunity o : Trigger.new) {

             Opportunity existingOpportunity= Trigger.oldMap.get(o.Id);
             allids.add(existingOpportunity);
        }

        for (Integer i = 0; i < Trigger.new.size(); i++) {
            if (Trigger.new[i].Billing_Report_Date__c != null && (Trigger.old[i].Billing_Report_Date__c == null || Trigger.old[i].Billing_Report_Date__c != Trigger.new[i].Billing_Report_Date__c)) {
                
   /* The select statement was inside the for loop and that is executing more than 20 times (the limit on SOQL queries within Apex triggers). 
                To remedy that, used an IN clause to compare multiple IDs at once, rather than using = in the SOQL WHERE clause.
                */
  Opportunity[] oppList = [Select o.tocClonedFrom__c, o.RecordType.Name, o.Id, o.Billing_Report_Date__c, (Select Id, Billing_Report_Date__c From Opportunities__r) From Opportunity o where o.tocClonedFrom__c in :allids or o.Id in :allids];
    
                for (Opportunity op : oppList) {
                    Opportunity opportunity = Trigger.oldMap.get(op.Id);
                    String id = opportunity.id;
                    if(op.RecordType.Name == 'Payment' && op.Id != id ) 
                    {
          Opportunity updOpp = new Opportunity(Id=op.Id);
                        updOpp.Billing_Report_Date__c  = Trigger.new[i].Billing_Report_Date__c;

                        updOpp.tfomsBillingCompleteDate__c = Trigger.new[i].tfomsBillingCompleteDate__c;
                        updOpp.tocBillingEffectiveDate__c = Trigger.new[i].tocBillingEffectiveDate__c;
                        toUpdate.add(updOpp);
                    }
                    if((op.tocClonedFrom__c == id && op.RecordType.Name == 'Payment') || op.Id == id )
                    {
                        tfCommissionOpportunity__c[] commOppsList = op.Opportunities__r;
                            for(tfCommissionOpportunity__c commOpp : commOppsList){
                            tfCommissionOpportunity__c updCommOpp = new tfCommissionOpportunity__c(Id=commOpp.Id);
                            updCommOpp.Billing_Report_Date__c = Trigger.new[i].Billing_Report_Date__c;
                        }
                    }
                }
                //Doing the bulk updates after the for loop due to the limit on SOQL queries within Apex triggers. 
  update toUpdate;
                update toComUpdate;
            }
            
        }
    } catch (Exception e){
        
    }
}

 
I could update upto 1700 Opportunities with the date values, but after that its showing the same SOQL error! :-(
 
I am unable to proceed,as i tried my level with using different approaches using Set,Map,List.. All of them failed.
 
I need to store ALL the Ids in a set or list regardless of any condition, this is where the problem occurs. I guess Apex is not able to take ALL the ids within IN clause? Or something else>
 
I am very much thankful to you people, if you could find the alternate solution or modify my code to get the bulk update for more than 20000 Opportunities
 
Please help me
thank you
jrotensteinjrotenstein
Do not use select or update within your loop. That makes too many DML calls.

Why are you involving 20,000 records? Are you trying to load them via the Data Loader? If so, you can reduce the batch size.

If your query really does need 20,000 records, then I think you're out of luck.

What are your actual business requirements? How often should this be called -- just once or on every update? What are you trying to accomplish?
KajaKaja
Hello ,
 
I could avoid this error which i was mentioned and updated 90% records successfully,
BUT now i am getting the different error
tocOportunityAfterUpdate: execution of AfterUpdate
caused by: System.Exception: Too many script statements: 50001
 
the following line causing this error.
tfCommissionOpportunity__c[] commOppsList = op.Opportunities__r;
 
Please provide your suggestions.
 
 
TLFTLF
You are basically running into one Apex governor limit after another. Because Salesforce is a multi-tenant environment, there are safeguards against code that tends to hog system resources. Please refer to the section titled "Understanding Execution Governor and Limits" in the Apex Language Reference: http://www.salesforce.com/us/developer/docs/apexcode/index.htm
 
As jrotenstein suggested, I think it would be better if you told us what it was you were trying to accomplish, then someone might be able to suggest alternatives.
KajaKaja
Hi John,
thank you for the response.
>>Why are you involving 20,000 records? Are you trying to load them via the Data Loader? If so, you can reduce the batch size.
I am loading the data using Data Loader only, just one time update. yes , i set the minimum the batch size as 200.
 
>>What are your actual business requirements? How often should this be called -- just once or on every update? What are you trying to accomplish?
 
The requirement is to update the billing dates for the correction opportunities. This trigger is written for this purpose, but while loading the bulk data update, it throws that error. It just updated only once.
I am trying to avoid ANY error while performing bulk upload from loader. My trigger should not throw any error due to bulk upload.
 
Now , please give me some hint, how to avoid this error Too many script statements: 50001
 
I have to get rid of this error from the history.
 
thanks
KajaKaja

Hi TLF,

I was going thru the link which you sent and got something about sforce execution Governor limits!

 I had put the select query outside the for loop and done one time update after the for loop only. So there is no problem in doing bulk update. As i mentioned before, I could update 95% records successfully,only 5% records are failed. Does my code causing this problem of updating this 5% failed records? When i try running the failed records,then 2% records updating rest 3% failed. Then again running 3% failed records, 2% passed and 1% failed.  

 Is there any other approach to process 100% records successfully at a single shot.

Here is my updated code. Request you to have a look at this and correct me, if i did something wrong with this code.

This is not created for the bulk update but for other purpose. This trigger caused the problem while doing bulk load thru Apex Data loader. Hope you people understand my problem.

Code:
trigger tocOportunityAfterUpdate on Opportunity (after update) {
    List<Opportunity> allids = new List<Opportunity>();
    List<Opportunity> toUpdate = new List<Opportunity>();
    List<tfCommissionOpportunity__c> toComUpdate = new List<tfCommissionOpportunity__c>();
    try {

        /*This for loop was redesigned to minimize the number of DML calls(select)
        Collecting the Opportunity objects that require updating in a List<Opportunity> and then doing a single update at the end of the for loop, 
        passing it the List<Opportunity> object.        */

        for (Opportunity o : Trigger.new) {

             Opportunity existingOpportunity= Trigger.oldMap.get(o.Id);
             allids.add(existingOpportunity);
        }
  /* The select statement was inside the for loop and that is executing more than 20 times (the limit on SOQL queries within Apex triggers). 
 To remedy that, used an IN clause to compare multiple IDs at once, rather than using = in the SOQL WHERE clause.
 */
 Opportunity[] oppList = [Select o.tocClonedFrom__c, o.RecordType.Name, o.Id, o.Billing_Report_Date__c, (Select Id, Billing_Report_Date__c From Opportunities__r) From Opportunity o where o.tocClonedFrom__c in :allids or o.Id in :allids];

        for (Integer i = 0; i < Trigger.new.size(); i++) {
            if (Trigger.new[i].Billing_Report_Date__c != null && (Trigger.old[i].Billing_Report_Date__c == null || Trigger.old[i].Billing_Report_Date__c != Trigger.new[i].Billing_Report_Date__c)) {
                

                for (Opportunity op : oppList) {
                    Opportunity opportunity = Trigger.oldMap.get(op.Id);
                    String id = opportunity.id;
                    if(op.RecordType.Name == 'Payment' && op.Id != id ) 
                    {
          Opportunity updOpp = new Opportunity(Id=op.Id);
                        updOpp.Billing_Report_Date__c  = Trigger.new[i].Billing_Report_Date__c;

                        updOpp.tfomsBillingCompleteDate__c = Trigger.new[i].tfomsBillingCompleteDate__c;
                        updOpp.tocBillingEffectiveDate__c = Trigger.new[i].tocBillingEffectiveDate__c;
                        toUpdate.add(updOpp);
                    }
                    if((op.tocClonedFrom__c == id && op.RecordType.Name == 'Payment') || op.Id == id )
                    {
                        tfCommissionOpportunity__c[] commOppsList = op.Opportunities__r;
                            for(tfCommissionOpportunity__c commOpp : commOppsList){
                            tfCommissionOpportunity__c updCommOpp = new tfCommissionOpportunity__c(Id=commOpp.Id);
                            updCommOpp.Billing_Report_Date__c = Trigger.new[i].Billing_Report_Date__c;
       toComUpdate.add(updCommOpp);
                        }
                    }
                }
                //Doing the bulk updates after the for loop due to the limit on SOQL queries within Apex triggers. 
  update toUpdate;
                update toComUpdate;
            }
            
        }
    } catch (Exception e){
        
    }
}


 

Awaiting for your valuable feedback! :-)

TLFTLF

It looks to me like you could be getting stuck in an infinite recursion situation, because you are trying to update Opportunities within a trigger that fires after update of Opportunity objects. This is from the Apex documentation:

Triggers can also modify other records of the same type as the records that initially fired the trigger. For example, if a trigger fires after an update of contact A, the trigger can also modify contacts B, C, and D. Because triggers can cause other records to change, and because these changes can, in turn, fire more triggers, the Apex runtime engine considers all such operations a single unit of work and sets limits on the number of operations that can be performed to prevent infinite recursion. See Understanding Execution Governors and Limits.

Does this have to be done in the context of a trigger? Can you do it from an Apex class instead of an Apex trigger?
KajaKaja
This trigger was written by some of old team member due to some purpose.
I've enhanced this trigger to make it work for bulk update without affecting the original functionality. I hope it doesn't break the original functionality which was written in my first message with the source code.
I may unable to replace this trigger by apex class.
 
Yes, this has to be done in the context of trigger. Can we alter the above trigger so that it doesn't cause any problem?
Or could you provide the alternative apex code to do this same functionality?
 
I am working on this for more than three days,but no progress still..
 
Please alter my code if it is wrong or something
 
KajaKaja

Boss,

Could you please help me on this how to update the Opportunities in bulk of 20000Records without having any SOQL Queries error. I am getting the error after updating 1700Records.

Though number of failed records only 400 ot of 21000, it is not correct. I have to update the trigger in such a way that it should not throw any too many SOQL  queries error or script elements error.

As per the apex documentation http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_gov_limits.htm , i should be able to retrieve as many as 20000 records, but failed even after 1700records.

Whats the resolution for this problem? Any change still i can do with my updated code?

As i am new to Apex class, i couldn't replace with apex class. Please guide me

regards

venkat

 

TLFTLF
I don't see any more obvious optimizations you can make to your trigger code. You may simply be trying to do too much in the context of a trigger. Apex triggers have tighter governor limits than Apex classes, that is why I suggested trying to do it outside of a trigger. Your Apex class would have to query to identify the Opportunity objects that need to be updated, and perform the same updates the trigger does.  
KajaKaja

You must be right Sir! :-)

Could you provide me the sample apex class with respect to the replacement of this trigger?

I am new to apex as i mentioned before. :-

Apex class should be added where and how it should be invoked by opportunity after update?

 

plz guide me sir

 

TLFTLF
Is this a one-time update, or is it something that you will need to do frequently? If it is a one-time update then you can simply create your Apex class and invoke your update method from the System log window. Your code won't be all that much different than your existing trigger. The big difference is that instead of receiving a list of updated objects, your Apex method will need to execute a select statement to query for the Opportunity objects that need to be updated. Since you result set will be bigger than 1000 objects, you'll need to execute this select statement in a for loop. See: "Working with Very Large SOQL Queries" in the Apex Language Reference for tips on how to deal with large result sets.
KajaKaja

It is BOTH one time update and will update the opportunities frequently. :-(

Once they would get the billing report dates, they would do the bulk update once in a month. Sometimes,update happen one by one. So i need to handle both. My concern is that it is start throwing 100 error records(caused by: System.Exception: Too many script statements: 30001) even after 1700 successful records update and it goes upto 400errors out of 21000 records.

Even if i set the batch size as 100 and QOQL query request as 100 in data loader, still behaving the same manner.
If i able to process 10000 records as a bulk update, then i can do update two times. but it thrown the error within 1700 records itself. What to do?
 
Would Apex class perform both the one time bulk update and frequent update individual opportunity object?
 
 
TLFTLF
The more I think about it, you may have to simply give up on the idea of using Apex for this. Even an anonymous Apex class or Visualforce controller limits you to a total of 10000 records retrieved and a total of 10000 DML statements (update, insert, etc.). I had a similar requirement to create/update as many as 20000 objects at a time. I implemented it as an external Java class that performs the update via the Web Services API. The Java class is invoked by an external scheduler. Sorry, but Apex is simply not well suited for doing mass updates because of the restrictive governor limits. You may also be able to do it from an S-Control and the AJAX toolkit, but that probably is not a great approach either, since you are basically doing the bulk of the work in the browser.
KajaKaja

We have come to some conclusion like you said.

During Bulk load, We can inactivate the trigger. Then do bulk load and then Activate the Trigger. This is straight forward job.

This is the standard way of doing in many companies.

Meanwhile, we are also finding if any alternatives are there.

Thanks a lot.