You need to sign in to do that
Don't have an account?
bohemianguy100
help bulkifying a trigger
I've written a trigger that is working fine for single record updates and inserts:
trigger OpportunityUpdateFromOppProduct on OpportunityLineItem (after insert, after update) { List<OpportunityLineItem> olis = [Select Id, OpportunityId, Service_Code__c, Is_Full_Service__c From OpportunityLineItem Where OpportunityId =: Trigger.new[0].OpportunityId]; Set<String> serviceCodeNames = new Set<String>(); for (OpportunityLineItem oli : olis) { serviceCodeNames.add(oli.Service_Code__c); } Map<String, String> scMap = new Map<String, String>(); for (Service_Code__c sc : [Select Name, Opportunity_Service_Field_Name__c From Service_Code__c Where Name in: serviceCodeNames]) { scMap.put(sc.Name, sc.Opportunity_Service_Field_Name__c); } Map<String, OpportunityLineItem> oliMap = new Map<String, OpportunityLineItem>(); Opportunity tmpOpp = [Select Id, Type From Opportunity Where Id =: Trigger.new[0].OpportunityId]; Map<Id, Opportunity> oppsToUpdate = new Map<Id, Opportunity>(); String oppServiceFieldName = null; string serviceFieldValue = null; Boolean alreadyExists; Boolean isFull = true; String str; if (tmpOpp.Type.contains('Syndicated')) { for (OpportunityLineItem oli : olis) { if (oliMap.containsKey(oli.Service_Code__c)) { alreadyExists = true; } else { alreadyExists = false; } oliMap.put(oli.Service_Code__c, oli); if (alreadyExists) { if (isFull != false) { oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } } else { str = oliMap.get(oli.Service_Code__c).Is_Full_Service__c; if (str == 'Full') { isFull = true; } else { isFull = false; } oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } oppsToUpdate.put(tmpOpp.Id, tmpOpp); } } update oppsToUpdate.values(); }
I was trying to bulkify the trigger so it would handle bulk updates using data loader. I modified the trigger to the following:
trigger OpportunityUpdateFromOppProduct on OpportunityLineItem (after insert, after update) { Set<Id> opIds = new Set<Id>(); for (OpportunityLineItem oli : Trigger.new) { opIds.add(oli.OpportunityId); } List<OpportunityLineItem> olis = [Select Id, OpportunityId, Service_Code__c, Is_Full_Service__c From OpportunityLineItem Where OpportunityId in: opIds]; Set<String> serviceCodeNames = new Set<String>(); for (OpportunityLineItem oli : olis) { serviceCodeNames.add(oli.Service_Code__c); } Map<String, String> scMap = new Map<String, String>(); for (Service_Code__c sc : [Select Name, Opportunity_Service_Field_Name__c From Service_Code__c Where Name in: serviceCodeNames]) { scMap.put(sc.Name, sc.Opportunity_Service_Field_Name__c); } Map<String, OpportunityLineItem> oliMap = new Map<String, OpportunityLineItem>(); Map<Id, Opportunity> mapOpp = new Map<Id, Opportunity>(); for (Opportunity o : [Select Id, Type From Opportunity Where Id in: opIds]) { mapOpp.put(o.Id, o); } Map<Id, Opportunity> oppsToUpdate = new Map<Id, Opportunity>(); String oppServiceFieldName = null; string serviceFieldValue = null; Boolean alreadyExists; Boolean isFull = true; String str; for (Id i : mapOpp.keyset()) { if (mapOpp.get(i).Type.contains('Syndicated')) { Opportunity tmpOpp = mapOpp.get(i); for (OpportunityLineItem oli : olis) { if (oliMap.containsKey(oli.Service_Code__c)) { alreadyExists = true; } else { alreadyExists = false; } oliMap.put(oli.Service_Code__c, oli); if (alreadyExists) { if (isFull != false) { oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } } else { str = oliMap.get(oli.Service_Code__c).Is_Full_Service__c; if (str == 'Full') { isFull = true; } else { isFull = false; } oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } oppsToUpdate.put(tmpOpp.Id, tmpOpp); } } } update oppsToUpdate.values(); }
It processes correctly for one record, but fails to update correctly for subsequent records.
I think the problem is occuring within this section of the code:
Set<String> serviceCodeNames = new Set<String>(); for (OpportunityLineItem oli : olis) { serviceCodeNames.add(oli.Service_Code__c); } Map<String, String> scMap = new Map<String, String>(); for (Service_Code__c sc : [Select Name, Opportunity_Service_Field_Name__c From Service_Code__c Where Name in: serviceCodeNames]) { scMap.put(sc.Name, sc.Opportunity_Service_Field_Name__c); }
Can anyone help with how I can correctly get this bulkified?
It looks like it's something small to me. If the service code already exists, you should only replace it if you're changing from "Partial" to "Full". You shouldn't ever update "Full" to "Partial". Here's the code section:
if (alreadyExists) { if (isFull != false) { oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { //** this will replace "Full" in some cases. serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } }
If you remove the line I noted above, it will only put "Partial" in there if alreadyExists is false, which I believe is the behavior you're looking for.
By the way, I don't understand what IsFull is meant to indicate. You're setting it at the end of the oli loop, which means it's going to apply to the next oli, and will not necessarily be accurate. Maybe you don't really need it?
Jeremy
All Answers
Hey bohemianguy,
Really, what you need to do is keep track of what service codes are on each opportunity separately. Which means you need to know which olis are on which Opportunity. One way to keep Opportunities and olis together is to do a subquery on Opportunity.
I left out the parts of your code that didn't seem like they would change much. Basically, though, you have to figure out how to index everything off of the Opportunity, which is a little complicated because it's not an Opportunity Trigger. See if what I said makes sense.
Jeremy
Hi Jeremy,
I think I understand your example, but I'm still not clear how I correctly get my service field codes from my lookup table.
I have to query the Service_Code__c table with the set of strings in the oppidtoservicecodesmap for each opportunity, so I have the following:
Now, I'm getting an error: Invalid bind expression type of SET<String> for column of type string, which is where I was trying to query all Service_Code__c object to get all the service field names I'm not clear on that part?
Thanks for the help. I appreciate it.
This part is your problem, because oppidtoservicecodesmap.values() is a list of sets, rather than one big set:
I can see a couple of ways to handle this. First, how many service codes do you have total? If it's not more than a couple of hundred, how about you just query all of them? That way you'll definitely have the ones you need.
Another way would be to carry a set of Service Codes through the loops:
It seems like either way would work. Let us know how it goes!
Jeremy
Hi Jeremy,
I only have about 50 different service codes (more will be added down the road), but it still isn't that many, so I queried all the service codes as you suggested. Here is the updated code:
After running several tests using dataloader with a batch size of 200, it looks to be processing correctly. I did notice one strange annomaly which occurs with deletes. If I have multiple opportunity products that have the same service code, but different values, the delete works on the first delete, but doesn't process the second delete correctly.
For example, say I have the following 4 opportunity product records for the same opportunity:
--------------Name--------------------- -Service code- -Is Full Service-
Pharmacy Management Trends and Profiles - PBM - True
Pharmacy Management Trends and Profiles - PBM - False
Pharmacy Management Trends and Profiles - PBM - False
Pharmacy Management Trends and Profiles - PBM - False
The opportunity field service name "PBM" will contain the value of "Full" because there is one record where the service code is True.
If I delete one of the opportunity products where "Is Full Service" is equal to false, PBM still remains Full on the opportunity, which is correct. Now, if I delete another of the opportunity products where "Is Full Service" is equal to false, PBM updates to partial, which is incorrect. There is still a an opportunity product where "Is Full Service" is equal to true and PBM should remain "Full" on the opportunity. If I edit the opportunity product, it will fix itself, but the delete doesn't seem to be working quite right. It only occurs if I delete more than one opportunity product that has the same service code.
Any idea what might be causing that?
Thanks for all your help!
It looks like it's something small to me. If the service code already exists, you should only replace it if you're changing from "Partial" to "Full". You shouldn't ever update "Full" to "Partial". Here's the code section:
if (alreadyExists) { if (isFull != false) { oppServiceFieldName = scMap.get(oli.Service_Code__c); if (oli.Is_Full_Service__c == 'True') { serviceFieldValue = 'Full'; } else { //** this will replace "Full" in some cases. serviceFieldValue = 'Partial'; } tmpOpp.put(oppServiceFieldName, serviceFieldValue); } }
If you remove the line I noted above, it will only put "Partial" in there if alreadyExists is false, which I believe is the behavior you're looking for.
By the way, I don't understand what IsFull is meant to indicate. You're setting it at the end of the oli loop, which means it's going to apply to the next oli, and will not necessarily be accurate. Maybe you don't really need it?
Jeremy
Thank you Jeremy...that was it. After removing that line and changing if(isFull != false) to if(isFull == false) it corrected the problem. It is working as expected now.
Thanks again for the help!