You need to sign in to do that
Don't have an account?
tschatz
Too many SOQL Queries Help Please!
Hi All,
I'm really banging my head with this one. I understand why I'm getting the error. My code is making too many SOQL queries. I've read the reference manual and understand that in some way I need to use Sets and Maps to get around making so many SOQL calls, but for the life of me, I can't figure out how to work this in to my code. To me it seems that I need to make one SOQL call to get a piece of information that I need to make the next SOQL call and so forth. Could someone PLEASE look at my code and show me using my code how I could change this to avoid the too many SOQL queries error? I've seen examples of how it's done but can not apply that to my code. My brain is not working in that direction so please don't point me to other examples. I'm not getting it.
Thank you in advance for your much appreciated help.
_t
Code:
public class CP_OpportunityClosedWon { public static void CP_createNewCPUser(Opportunity[] opps){ String cName; Contact c; OpportunityLineItem[] oli = new OpportunityLineItem[]{}; //initialized to fix null pointer error for (Opportunity o:opps){ if (o.Order_Status__c == 'Shipped' || o.Order_Status__c == 'Definite') { ID cID = [select contactid from opportunitycontactrole where role = 'Ship To' AND opportunityid = :o.id].contactId; c = [select id, title from contact where id = :cID]; oli = [select id from opportunitylineitem where opportunityid = :o.Id]; } } //oli is null if the status is not shipped or definite which causes an exception here for (OpportunityLineItem li : oli){ ID pID = [Select o.PricebookEntryId, o.PricebookEntry.Product2Id from OpportunityLineItem o where id = :li.id].PriceBookEntry.Product2Id; if(pID != null){ Product2 p = [select id,name,downloadable__c from product2 where id = :pID]; if(p.downloadable__c == true){ c.Portal_User__c = true; c.Portal_URL__c = c.title; update c; } } } } }
For each input Opportunity whose status is 'Shipped' or 'Definite',
- Grab the associated contacts thru the Opp's OpportunityContactRole related list,
whose Role is 'Ship To'
- Look for any line items thru the Opp's OpportunityLineItem detail list,
whose PriceBookEntry reference in turn refers to a Downloadable Product2
- If there are any such Downloadable line items,
- Mark each of the above list of contacts as 'Portal User'.
Is this more or less correct?
for example
Select o.Opportunity.Amount, o.Opportunity.StageName, o.OpportunityId, o.Contact.LastName, o.ContactId From OpportunityContactRole o
will pull data that you need from 3 tables and avoid multiple queries
in the second loop, consider this relationship query
Select p.Product2.IsActive, p.Product2.ProductCode, p.Product2.Name, p.Product2Id, (Select OpportunityId From OpportunityLineItems where totalprice > 0.0) From PricebookEntry p
where you get product2 custom fields and opportunity line item fields from a single query
hope this helps, you still have to work this into your code, but relation queries are very powerful, you should use them.
Here's an example of how you could go about this, with only three SOQL statements (no relationship queries) and one DML.
Note: don't expect this code to compile first time round, but it should be pretty close to workable, and I think the underlying ideas
are sound. It might be possible to optimize the logic further, but not too much further.
-philbo
public class CP_OpportunityClosedWon {
public static void CP_createNewCPUser(Opportunity[] opps){
Set<Id> oppIdSet = new Set<Id> {};
// Grab only the opportunities with Shipped or Definite status.
// ------------------------------------------------------------
for (Opportunity o:opps){
if (o.Order_Status__c == 'Shipped' || o.Order_Status__c == 'Definite') {
oppIdSet.add ( o.Id );
}
}
if ( oppIdSet.isEmpty () ) {
return; // nothing to do!
}
// Get a set of Contact Ids for each of these Opportunities, with Ship To role.
// ----------------------------------------------------------------------------
Map<Id , Set<Id>> contactIdsByOppId = new Map<Id , Set<Id>> {};
for ( OpportunityContactRole ocr : [select OpportunityId , ContactId
from OpportunityContactRole
where Role = 'Ship To'
and OpportunityId in :oppIdSet
] ) {
if ( !contactIdsByOppId.containsKey ( OpportunityId ) ) {
contactIdsByOppId.put ( OpportunityId , new Set<Id> {} );
}
contactIdsByOppId.get ( OpportunityId ).add ( ContactId );
}
// Inspect the Opportunity Line Items for each of these Opportunities, looking for Line Items
// associated with Downloadable Products.
// If a Downloadable Line Item is found, mark the corresponding Opportunity's Contacts for
// update.
// ------------------------------------------------------------------------------------------
Set<Id> updContactIdSet = new Set<Id> {};
for OpportunityLineItem oli : [select OpportunityId , PriceBookEntry.Product2.Downloadable__c
from OpportunityLineItem
where OpportunityId in :oppIdSet
] ) {
if ( oli.PriceBookEntry.Product2 != Null
&& oli.PriceBookEntry.Downloadable__c == True ) {
updContactIdSet.addAll ( contactIdsByOppId.get ( OpportunityId ) );
}
}
// Finally, grab the Contacts to update, and apply the updates.
// ------------------------------------------------------------
Contact[] updContactList = [select Id , Title , Portal_User__c , Portal_URL__c
from Contact
where Id in :updContactIdSet
];
for ( Contact c : updContactList ) {
c.Portal_User__c = True;
c.Portal_URL__c = c.Title;
}
update updContactList;
}
}
Thanks to all who added to this thread. Your comments and advice is appreciated
Message Edited by tschatz on 03-27-2008 12:42 PM
* 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 take it to mean that if the limit is queries 20, you can have upto 60 relationship queries??. - so it does help to write relationship queries!!
.