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
Rick_NookIndRick_NookInd 

Too many SOQL queries?

We went Live with SF today, and I'm getting Too many SOQL queries error on a trigger of mine.

 

The trigger is fairly simple in concept in that it takes the ownership of an opportunity and makes certain related objects (or certain objects related to those related objects) and makes the owner of the record the same.

 

In implmentation, I had to do maps and limit the scope of of things with comparisions to keep under the limits and I succeeded until now. 

 

But I'm making no sense of this error message.  At my count there are at most 12 SOQL queries in my code, none in any sort of loop.  So..... how is it adding up to 20?  Is it counting Workflows, Triggers that trigger other triggers, triggers that trigger other triggers that trigger this trigger again?  What are the exception to the rule in which I can't just count the number of potential queries in a given query code to see if I'm going to exceed this SOQL limit?

 

Here's the code:

trigger Opportunity_Ownership_Trigger on Opportunity (after insert, after update) {
    List<String> theIds = new List<String>();
    for(Opportunity opp : Trigger.new) {
          theIds.add(opp.Id);
      }
      boolean updated = false;
     
      List<Order_Detail__c> od_list = [SELECT Id,Opportunity__c,OwnerId,Order__c FROM Order_Detail__c WHERE Opportunity__c IN :theIds];
      for(Order_Detail__c od : od_list) {
        if (Trigger.newMap.get(od.Opportunity__c).OwnerId != od.OwnerId) {
              od.OwnerId = Trigger.newMap.get(od.Opportunity__c).OwnerId;
              updated = true;
        }
      }
      if (updated)
      {
          update od_list;
      }
      Map<Id, Order_Detail__c> od_map = new Map<Id, Order_Detail__c>([SELECT Id,Opportunity__c,OwnerId,Order__c FROM Order_Detail__c WHERE Opportunity__c IN :theIds]);
      Map<Id, Order_Detail__c> od_order_map = new Map<Id, Order_Detail__c>();
      for(Order_Detail__c od : [SELECT Id,Opportunity__c,OwnerId,Order__c FROM Order_Detail__c WHERE Opportunity__c IN :theIds]) {
          if(!od_order_map.containsKey(od.Order__c)) {
              od_order_map.put(od.Order__c,od);
          }         
      }
 
      updated = false;
     
      List<Invoice_Detail__c> id_list = [SELECT Id,Opportunity__c,OwnerId,Invoice__c FROM Invoice_Detail__c WHERE Opportunity__c IN :theIds];
      for(Invoice_Detail__c id : id_list) {
        if (Trigger.newMap.get(id.Opportunity__c).OwnerId != id.OwnerId) {
              id.OwnerId = Trigger.newMap.get(id.Opportunity__c).OwnerId;
              updated = true;
        }
      }
      if (updated)
      {
          update id_list;
      }
      Map<Id, Invoice_Detail__c> id_map = new Map<Id, Invoice_Detail__c>();
      for(Invoice_Detail__c id : [SELECT Id,Opportunity__c,OwnerId,Invoice__c FROM Invoice_Detail__c WHERE Opportunity__c IN :theIds]) {
          if(!id_map.containsKey(id.Invoice__c)) {
              id_map.put(id.Invoice__c,id);
          }
      }
     
      updated = false;
      theIds = new List<String>();
    for(Order_Detail__c od : od_list) {
          theIds.add(od.Id);
      }
      List<Order_Release__c> or_list = [SELECT Id,OwnerId,Order_Detail__c FROM Order_Release__c WHERE Order_Detail__c IN :theIds];
      for(Order_Release__c or_item : or_list) {
        if (od_map.get(or_item.Order_Detail__c).OwnerId != or_item.OwnerId) {
              or_item.OwnerId = od_map.get(or_item.Order_Detail__c).OwnerId;
              updated = true;
          }
      }
      if (updated)
      {
          update or_list;
      }
     
      updated = false;
      theIds = new List<String>();
    for(Order_Detail__c od : od_list) {
          theIds.add(od.Order__c);
      }     
      List<Sales_Orders__c> o_list = [SELECT Id,OwnerId FROM Sales_Orders__c WHERE Id IN :theIds];
      for(Sales_Orders__c o : o_list) {
        if (od_order_map.get(o.Id).OwnerId != o.OwnerId) {
              o.OwnerId = od_order_map.get(o.Id).OwnerId;
              updated = true;
          }
      }
      if (updated)
      {
          update o_list;
      } 
         
      updated = false;
      theIds = new List<String>();
    for(Invoice_Detail__c id : id_list) {
          theIds.add(id.Invoice__c);
      }     
      List<Invoice_Object__c> i_list = [SELECT Id,OwnerId FROM Invoice_Object__c WHERE Id IN :theIds];
      for(Invoice_Object__c i_item : i_list) {
        if (id_map.get(i_item.Id).OwnerId != i_item.OwnerId) {
              i_item.OwnerId = id_map.get(i_item.Id).OwnerId;
              updated = true;
          }
      }
      if (updated)
      {
          update i_list;
      }       
}

 

Additionally these triggers support that trigger by invoking it appropriately, but do not error out:

trigger Opportunity_Ownership_Trigger_Order_Detail on Order_Detail__c (after insert, after update) {
    List<String> theIds = new List<String>();
    if (Trigger.isInsert) {
        for (Order_Detail__c od : trigger.new) {
            if (od.Opportunity__c != null) {
                theIds.add(od.Opportunity__c);   
            }   
        }
    }
    else if (Trigger.isUpdate) {
        for (Order_Detail__c od : trigger.new) {
            if (Trigger.oldMap.get(od.Id).Opportunity__c != od.Opportunity__c) {
                if (od.Opportunity__c != null) {
                    theIds.add(od.Opportunity__c);     
                }
            }
        }
    }
    List<Opportunity> opp_list = [SELECT Id FROM Opportunity WHERE Id IN :theIds];
    update opp_list;
}

trigger Opportunity_Ownership_Trigger_Invoice_Detail on Invoice_Detail__c (after insert, after update) {
    List<String> theIds = new List<String>();
    if (Trigger.isInsert) {
        for (Invoice_Detail__c od : trigger.new) {
            if (od.Opportunity__c != null) {
                theIds.add(od.Opportunity__c);   
            }   
        }
    }
    else if (Trigger.isUpdate) {
        for (Invoice_Detail__c od : trigger.new) {
            if (Trigger.oldMap.get(od.Id).Opportunity__c != od.Opportunity__c) {
                if (od.Opportunity__c != null) {
                    theIds.add(od.Opportunity__c);     
                }
            }
        }
    }
    List<Opportunity> opp_list = [SELECT Id FROM Opportunity WHERE Id IN :theIds];
    update opp_list;
}

Best Answer chosen by Admin (Salesforce Developers) 
WilmerWilmer
Please see this case, it perhaps is similar to yours. See this Case (Click on this Link)

All Answers

jpwagnerjpwagner

Without thoroughly reading your code...

 

yes all apex code that (calls/triggers) causes other apex code contributes to the governor limits.  so you have a bunch of updates that cause other triggers to run.  If you're up to 12 already without taking this into account, you'll surely be over 20 very often.

 

make sure that you filter out what might be called irrelevant updates (if there's an update, but no field value changes etc...)

WilmerWilmer

Hi Rick_NookInd,

 

Check if you have any workflow on the object which modifies the value of a record's field, this makes the trigger run again and then limits are overpassed. By the other side, check if there are other triggers on Opportunity object.

 

Regards,

 

Wilmer

 

 

werewolfwerewolf

Yes, remember that the query limit is additive -- if you have a trigger before this that eats 9 queries and then this one tries to use 12, it will fail on the 12th one, and this particular trigger will throw the error.

 

And boy, you have a lot of opportunities to prune queries in there anyway.  For one thing, you're doing this one three times:

 

[SELECT Id,Opportunity__c,OwnerId,Order__c FROM Order_Detail__c WHERE Opportunity__c IN :theIds]

 

More generally, though, have you perhaps considered using SOQL Relationships?  You could get a lot of this stuff in one query, like

 

[SELECT  (SELECT Id,Opportunity__c,OwnerId,Order__c FROM R00N30000002X9AGEA0) From Opportunity__c WHERE Id IN :theIds]

 

That looks a little weird because the relationship specifier uses an ID -- use the Force.com IDE's query builder to help you build those. 

 

In any case: your code is ripe for pruning queries.

jpwagnerjpwagner

Werewolf,

 

Can you please confirm that...

 

[SELECT  (SELECT Id,Opportunity__c,OwnerId,Order__c FROM R00N30000002X9AGEA0) From Opportunity__c WHERE Id IN :theIds]

 

and

 

[SELECT  Id,Opportunity__c.Owner.Id,Opportunity__c.Account.Name From Opportunity__c WHERE Id IN :theIds]

 

...each counts as 1 query towards the SOQL query governor limit?

 

I just want this to be crystal clear because I thought joins and "Relationship Queries" counted as more than one query.

 

Thanks!

Message Edited by jpwagner on 04-15-2009 07:10 PM
werewolfwerewolf

You have a point, but the docs say:

 

In a SOQL query with parent-child relationship sub-queries, each parent-child relationship counts as an additional query. These types of queries have a limit of three times the number for top-level queries. The row counts from these relationship queries contribute to the row counts of the overall script execution.

 

I read that to mean you get 3x the query limit for those child relationships, or that a child relationship in the query counts as 1/3 of a query.

jpwagnerjpwagner

Great!  Thanks for the clarification.  That means: 

 

[SELECT  (SELECT Id,Opportunity__c,OwnerId,Order__c FROM R00N30000002X9AGEA0) From Opportunity__c WHERE Id IN :theIds]

 

= 1.33 queries 

 

and

 

[SELECT  Id,Opportunity__c.Owner.Id,Opportunity__c.Account.Name From Opportunity__c WHERE Id IN :theIds]

 

= 1.67 queries (because of the join to Owner and to Account)

 

 

 

If this is true, the only thing left unclear is why you never see:

"Too Many SOQL queries: 20.33"

 

 

Is the limit actually 21?  or are we misinterpretting the rule?

Rick_NookIndRick_NookInd

So I can have the trigger "do nothing", the 2nd time it is run for a given "update", i.e. shared governor limit...

 

How do I detect in the trigger if the trigger is being triggered off a workflow?

 

How do I detect in the trigger if the trigger is being triggered off a rollup value changing?

 

Is there anything else (other than other Apex code which I can plan for) that might "trigger" a trigger?

 

The fact that each trigger execution does not have it's own independent limit seems ridiculous to me in this moment. I think for best practices I may have to add such checks to every trigger I write.

jpwagnerjpwagner

The origin of the update that fires the trigger does not matter.

 

The governor limits are there to guard against exactly what you are doing in your triggers...one object update causes another and back...in a loop.

 

What are you trying to achieve?

The use-case is more helpful than the code, so that others can help you design it.

Message Edited by jpwagner on 04-16-2009 07:30 AM
Rick_NookIndRick_NookInd

Similar to the My Accounts and My Opportunities views, I have a My Quotes, My Orders, and My Invoices views for custom objects.  The ownership of those Quotes, Quote Lines, Order, Order Lines, Order Releases, Invoices, and Invoice Lines are wholely determined by who owns the opportunity related to it, if there is one related.  The trigger sets and maintains such ownership.

 

It worked great, but then workflows were added that sets the stage of an Opportunity in certain circumstances and the amount of the Opportunity in certain circumstances which runs the trigger multiple times on the same governor limit.  For example, lets say an Order is placed from a Quote, the Order must be owned by the owner of the Opportunity associated by the Quote, the stage of the Opportunity must be set to Closed Won, and the value of the Opportunity must be set to the value of the order.  Independently they work great, but all at once and the two workflows run the ownership trigger multiple times and governor overflow.

werewolfwerewolf

You might think about doing this work in an @future method instead.  @future methods run asynchronously sometime after the trigger (usually immediately in my experience), and the limits on them are more relaxed.  This will also relieve the interplay between the triggers, since your @future method invocation's queries won't count against the trigger's limit.

 

Here's an example of how to use @future.

Rick_NookIndRick_NookInd

I looked into @future, and there are some serious limits on it.  Specifically, only 200 @future calls per license per day.  And, our triggers are apparently called a lot (multiple times per record updated).

 

I'm just may to have to redesign it to not be in a trigger and be batch.  It'll end up putting more load on their server than less as it may have to update every record instead of as needed but at least it may work.  Now I just have to figure how to schedule Apex code to run.  *sigh*

 

Thanks for all the advice so far all.

werewolfwerewolf

Well, your first step is to optimize the trigger you have.  As I mentioned, you have some low-hanging fruit there, like that query that's done thrice.

 

Apex cannot yet be run in batch (although you can use CronKit from the AppExchange to do some semblance of that).

WilmerWilmer
Please see this case, it perhaps is similar to yours. See this Case (Click on this Link)
This was selected as the best answer
Rick_NookIndRick_NookInd

I rewrote the triggers to limit the amount of queries.  I now need 6 triggers instead of 3, but the 1 violating the limits I expect will no longer do so.  The key difference is that it only needs to run when the ownership of an opportunity changes instead of every time an opportunity changes.  So I guess I sort of spread the work out among different triggers.  Seems to work so far, but we shall see.

 

trigger Opportunity_Ownership_Trigger on Opportunity (after insert, after update) {

List<String> theIds = new List<String>();

if (Trigger.isUpdate) {

for(Opportunity opp : Trigger.new) {

if (Trigger.oldMap.get(opp.Id).OwnerId <> opp.OwnerId)

{

theIds.add(opp.Id);

}

}

}

else if (Trigger.isInsert) { for(Opportunity opp : Trigger.new) {

theIds.add(opp.Id);

}

}

if (!theIds.isEmpty()) {

List<Order_Detail__c> od_list = [SELECT Id,Opportunity__c,OwnerId FROM Order_Detail__c WHERE Opportunity__c IN :theIds];

for (Order_Detail__c od_item : od_list) {

od_item.OwnerId = Trigger.newMap.get(od_item.Opportunity__c).OwnerId;

}

update od_list;

List<Invoice_Detail__c> id_list = [SELECT Id,Opportunity__c,OwnerId FROM Invoice_Detail__c WHERE Opportunity__c IN :theIds];

for (Invoice_Detail__c id_item : id_list) {

id_item.OwnerId = Trigger.newMap.get(id_item.Opportunity__c).OwnerId;

}

update id_list;

/* and three other business objects when I finish */

}

}

And then five triggers almost identical to this:

trigger Opportunity_Ownership_Trigger_Order_Detail on Order_Detail__c (before insert, before update) {

List<String> theIds = new List<String>();

if (Trigger.isUpdate) {

for(Order_Detail__c od_item : Trigger.new) {

if (Trigger.oldMap.get(od_item.Id).Opportunity__c <> od_item.Opportunity__c)

{

theIds.add(od_item.Opportunity__c);

}

}

}

else if (Trigger.isInsert) { for(Order_Detail__c od_item : Trigger.new) {

theIds.add(od_item.Opportunity__c);

}

}

if (!theIds.isEmpty()) {

Map<Id, Id> opp_map = new Map<Id,Id>();List<

Opportunity> opp_list = [SELECT Id,OwnerId FROM Opportunity WHERE Id IN :theIds]; for (Opportunity opp : opp_list) {

opp_map.put(opp.id,opp.OwnerId);

}

for(Order_Detail__c od_item : Trigger.new) { if (opp_map.containsKey(od_item.Opportunity__c)) {

od_item.OwnerId = opp_map.get(od_item.Opportunity__c);

}

}

}

}

 

Rick_NookIndRick_NookInd
Thats exactly what I needed Wilmer!  Wish you had posted that just a little quicker so I didn't have to reimagine and rewrite the triggers. :/  But thanks regardless.  I'll use it when I run into the same problem again which I'm sure I will.