Preface: this post is part of the Advanced Apex Concepts series.
Sometimes you only want to run a trigger when a field changes to a certain value, for example:
In the first example, simply writing a trigger that checks for a certain stage won’t cut it!
Ex: using if (opp.StageName = ‘Closed Won’) is not a good solution since it’ll run every time a Closed Won opp is updated – whether or not it was already Closed Won or changed to Closed Won!
If you’re familiar with workflows, the kind of criteria we want to emulate is the
“created, and any time it’s edited to subsequently meet criteria” option.
To understand the trick to properly doing this, you have to know how to use Maps!
// Check a checkbox only when an Opp is changed to Closed Won! trigger Winning on Opportunity (before update) { for (Opportunity opp : Trigger.new) {// Access the "old" record by its ID in Trigger.oldMap Opportunity oldOpp = Trigger.oldMap.get(opp.Id);// Trigger.new records are conveniently the "new" versions! Boolean oldOppIsWon = oldOpp.StageName.equals('Closed Won'); Boolean newOppIsWon = opp.StageName.equals('Closed Won'); // Check that the field was changed to the correct value if (!oldOppIsWon && newOppIsWon) { opp.I_am_Awesome__c = true; } } }
Note that Trigger.oldMap is just one of the special Trigger context variables available to you!
Next post: Sending emails using Apex!
Why did you create 2 boolean variables and compared them, instead of directly comparing the fields from trigger.new and trigger.oldmap like this:
oldOpp.StageName != Opp.StageName
The way you have written it the trigger would happen anytime the value of StageName changes. For example, if you were to change the StageName from ‘Needs Analysis’ to ‘Negotiation’, your check would pass because the old value is different than the new value. We do not want this behavior.
The way it is written checks and verifies that the new value equals ‘Closed Won’ and the old value was not ‘Closed Won’. We not only need to check if the values are different, but we need to check the specific values of the variables, meaning both comparisons are necessary.
to make it more simple, you could use the Boolean isWon. In that case, vatsal suggestion would work like it show.
Actually, the Boolean isWon would not be set yet as the above example is a Before event. But had it been an After event, then absolutely use isWon!
Hi Nate ,
Could you tell how it should be written . I am new to maps
Regards,
Arunima
Hi David,
Can I use a trigger to update a total hours field on a custom object ‘Contract’ from time entered from another non-related custom object ‘Task Time’? They are ‘linked’ together by Account Id and a Category.
I am not an apex coder, so I apologize up front if this is a stupid question!
Thanks!!
Kris
hi kris,
you can use triggers to update unrelated objects.bt what u meant by “linked together by account id and category”?
Hi,
Here i am having a checkbox field Restrict Date and Modify Date is an Date field.
scenario : If Checkbox field is true then update Modify date to today()
How can i do this workflow rule using formula criteria.
Also i need to check the old value.
Can i do this using Workflow rule
Please help me here iam new to salesforce.
Hi David,
Always fun working with you. I followed your suggestions and my trigger is not working. It gives me an error “execution of BeforeUpdate caused by System.FinalException: Record is read-only: ()”. Here is my trigger:
trigger stillTryng on Account (before update) {
for( Account acc : trigger.new){
// Access the “old” record by its ID in Trigger.oldMap
Account oldAcc = Trigger.oldMap.get(acc.Id);
// Trigger.new records are conveniently the “new” versions!
Boolean oldAccWpf = oldAcc.CR_Parent_Fax__c=False;
Boolean newAccWOpf= acc.CR_Parent_Fax__c=True;
// Check that the field was changed to the correct value
if(!oldAccWpf && newAccWOpf){
acc.In_Active_Account__c = true;
}
}
}
I am not able to find anything wrong with it. Maybe you can. I would appreciate any help.
Many thanks in advance.
Miranda-
Hey Miranda hope you’ve been well!!
Check out this post!
https://www.sfdc99.com/2013/10/12/comparison-operators/
hi david ,
My opportunity stage sequence is (pick list):Qualification,Preparation,Presentation,Demonstration,Negotiation,Acquisition,Order,Execution,Qualified Out,won,lost
The user needs to go this sequence if suppose if he goes from qualification to presentation there he missed sequence whenever he missed the sequence i want to enable the checkbox of stages missed field or i want to track what stages he missed in the custom field of stages missed
David reply back how can i do this if trigger means tell me logic how can i Acheive this
Hi David,
what is the difference b/w my trigger and your trigger. Could you please explain need of map in your trigger.
I have big question- with out using trigger.oldmap can we update old records in before trigger
trigger Winning on Opportunity (before update) {
for(opportunity op : trigger.new){
if(op.stagename == ‘closed won’){
op.I_am_Awesome__c = true;
}
}
}
Say you had a bunch of Opportunities enter the trigger because they were all updated. For each opp, it will check if stage name is currently true, and if it is, it ticks the box.
Half your Opportunties you really did change it to Closed Won, so it Trigger is correct for them. But the other half were already on Closed Won, you just changed the Name field, yet it still entered the trigger and fired the checkbox update.
Using maps as he did makes sure the trigger is efficient and only does actions (checks the box) when it truly needs to.
Hi David,
I am new to Salesforce Apex. I want to calculate the sum of total amount on all the Opportunities belong to a similar campaign but I am getting too Many Queries Error as there exist more than 50,000 Opportunities on a campaign.
How can I do that ?
That’s a lot of records! Consider using batch Apex and/or summary objects!
Hi, I want to write a trigger. trigger should fire only when my field ‘dateVerified__c’ is changed and after that I have to check if accounts having any related contacts then it should check a checkbox ‘isContactAvailable__c’
if u want then u can do something like that to fetch contact records:”
list acts=new list();
for(Account a:[select id from Account where id in(select Accountid from contact )AND ID in:trigger.old]){
trigger.oldmap.get(a.id).adderror(‘it has related contacts’);”
Hi
I want to add condition on the bases of stage change which should not be fired when changing the value second time.
Example. Firstly Stage = Build then some logic needs to be executed,
Secondly change Stage from Build to Complete then some logic will execute.
Now when changing from Complete to Build no logic should execute and similarly for Build to Complete. No logic should execute as many times user is changing from build to complete or complete to build.
Please tell me how to satisfy this requirement.
Hi David,
If I want to compare a field among the two most recent records only, can I use trigger.old? Does trigger.old capture the value in the same record before update or the prior/previous record before insert? It seems capturing the same record before update…
Do you have good ideas about how to compare a field value between the newest record and the second newest record?
Thanks,
Jolie Tan
Ditch the Trigger.old and compare object1.field__c == object2.field__c
You are awesome!
Great!
Thanks for the help and insight! I did include a debug statement, and testing the trigger. The debug log indicated that it populated the list with the correct records per the SOQL query. However, it still didn’t check the box…is there a confirmation line in the Debug log that I’m failing to see?
Try updating another field to see if it works there =)
Hey David! Your site has helped me a ton!
I’m having some trouble writing this trigger. My goal here is to find all the Transactions less than a year old and change the Checkbox field (12_month_trade__c) to true. Seems pretty simple but its not working…
trigger rolling_window_update on Transaction__c (before update) {
List buyTrades =[Select Id FROM transaction__c WHERE EffectiveDate__c = LAST_N_DAYS:365];
for (Transaction__c twelveMonth : buyTrades) {
twelveMonth.X12_Month_trade__c = true;
}
}
thanks!
Chris
Honestly that looks like it should work!
A couple recommendations though:
– Debug using System.debug
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
– Your trigger is going to run on every single record every time any Transaction__c is changed – very inefficient (but will still work in the short term)
Hi David –
You said:
**– Your trigger is going to run on every single record every time any Transaction__c is changed – very inefficient (but will still work in the short term)**
What’s a good way to avoid this problem?
Could the SOQL statement be modified so the WHERE statement excludes records where X12_Month_trade__c = true (ie AND X12_Month_trade__c 1= true)?
Thanks!
Josh
Josh,
That’s actually a fantastic idea! I am in shock right now as it’s better than the one I came up with! Not even gonna say it!
How did the System.debug go?
David
Hey David –
Wow, I impressed you! That’s a huge compliment!!
I’m actually not the original poster here.
I just saw this question and it piqued my interest.
I spend A LOT of time in Salesforce reporting (I love reporting, btw), so I think that experience helped here. A lot of reporting is figuring out how to exclude records from the report, which I guess translated well to this question:)
Thanks!
Josh
One other thing, I wanted to fix the code above in case it confuses anybody, especially those newer to code.
It shouldn’t read 1= true, it should have been != true, in other words:
AND X12_Month_trade__c != true
Great Lesson David…Enjoying …Learning……thanks a lot !! :)
Hi David ,
Can you please answer my query,
if I update a record with checkbox is checked , I want to uncheck checkbox of all other records .
How can I achieve it
You’ll need this post, plus SOQL in your trigger =)
Give it a shot and post it on our forums!
I created a couple helper methods to simplify this. You can check out the gist: https://gist.github.com/omniphx/90e9056feb93fb31a8f4 or read more about it on my website: http://www.mattmitchener.com/post/simplifying-field-change-criteria-on-apex-triggers
Thanks for the info Matt!
Does the Trigger context variable oldMap means that it stores ALL the previous versions of the record? Eg. If the Opportunity record after been created was updated 2 times, olMap will have a map.size() of 3?
How would you write a test class for a trigger like the one in the example above?
Never mind David I was able to think through it and figure it out on my own! I am so proud of myself!!!!!
Proud of you too Ronnie!
can you please share text class
opp.I_am_Awesome__c = true;
David, I am curious about the line above. Are we to presume that the custom field “I_am_Awesome__c” already exists in the Opportunity sObject; or is it that Salesforce allows the creation of custom fields on the fly using Apex codes.
I was also curious about the same thing in chapter 4’s DetectDupe trigger where you used the custom fields “Potential_Contact_Dupe__c” and “Potential_Lead_Dupe__c”.
Thought it best to check with you instead of assuming anything. I am completely new to Salesforce and Apex (as in I have never used the platform professionally) so I am not very familiar with the different sObjects and what fields they contain.
Thanks.
Hey Felix,
Definitely the former, ie, the custom field “I am Awesome” has already been created! Creating a new field using Apex is quite difficult, never done it actually! If you wanted to do it you’d probably use the Metadata API.
You are learning very well!!
David
Hey David !
Before reading this post i always wrote this type of code ” if (opp.StageName = ‘Closed Won’) ” .
but now i know what to do when i am in this type of situation.
Thanks ! :-)
Viru
Sweet! Happy to see you learning Viru!!
Hi David –
Great work you’re doing here.
I’m having trouble wrapping my head around part of the code above.
The part that’s confusing me is where you set the Boolean value of “oldOppIsWon”:
Boolean oldOppIsWon = oldOpp.StageName.equals(‘Closed Won’);
And then later test if “oldOppIsWon” is not true (eg not ‘Closed Won’)
if (!oldOppIsWon && newOppIsWon) {
But since above you already set the value of “oldOppIsWon” to the Stage ‘Closed Won’, this would already be true.
So clearly I’m missing something here with what’s going on with the setting of the Boolean values. Can you help me here with what I’m missing?
Thanks!
Josh
Hey Josh,
There’s no guarantee that oldOppisWon is true! If the old Opp’s Stage is ‘Closed Won’, the Boolean value will come out to true. But if it’s any other Stage, it’ll be false.
Hope this helps!
David
Thanks for the response David.
To make sure I understand, the .equals() method isn’t used to set a value (ie setting the Opportunity Stage to ‘Closed Won’), it just returns a boolean value. Am I right here?
Thanks!
Josh
100% true! One equals (=) will set a value while .equals or two equals (==) will do a true/false comparison!
Hi david i am learning to write apex code i think i am some whaat perfect in that now. but how to write a visualforce code and controllers in salesforce
Haven’t gotten to those chapters yet unfortunately!!
Are you able to do all the quizzes without errors? If so, start with Visualforce using the standard documentation!
Hi David,
Is it possible to carry dot notation out as far as shown below? I am just curious.
Boolean oldOppIsWon = Trigger.oldMap.get(opp.id).StageName.equals(‘Closed Won’);
Boolean newOppIsWon = Trigger.oldMap.get(opp.id).StageName.equals(‘Closed Won’);
Tom B.
Heck yea it is! You can keep going essentially forever!
The reason I didn’t do it in this example is because I try reallllly hard in Sfdc99 to keep every statement in one line of code for readability reasons.
Hope this helps!
David
I agree with you David — KISS – is always the best approach. I just wanted to know out of curiosity sake concerning the dot notations capabilities. as always, thank you so much for your generosity!
Hi David,
If we will use like below:
Boolean oldOppIsWon = Trigger.oldMap.get(opp.id).StageName.equals(‘Closed Won’);
Boolean newOppIsWon = Trigger.oldMap.get(opp.id).StageName.equals(‘Closed Won’);
It will give old version value in ‘oldOppIsWon’ variable but in in ‘newOppIsWon’ variable also it will give old version value as we are using oldMap context variable for both statement.
So, if we are getting old version value in both variable then how we can compare old version with new version?
Please correct me here.
Thank you,
Amit
You are correct – both versions you’ve listed will have the exact same values (the old value) no matter what!
You can either use the method I used in the post to avoid that, or you can simply use Trigger.newMap.
David
Hi Daniel,
What is this specific line of code doing?
if (!oldOppIsWon && newOppIsWon) {
It’s comparing the Stage of the old and new trigger values right? but what is the logical argument for the if statement?
Thanks,
Brian
Great question!
Using a Boolean variable is exactly the same as doing a Boolean comparison, ie these two statements are exactly the same:
Boolean test = true;
Statement 1: if (test)…
Statement 2: if (test == true)…
Also the exclamation point simply takes the opposite of the statement, so these two statements are the same:
!test
test == false
So basically the statement you’re referring to says:
“if the old opp is NOT won and the new opp is won” aka
if (oldOppIsWon == false && newOppIsWon == true)
Some more background in this post:
https://www.sfdc99.com/2013/10/12/comparison-operators/
Hope this helps!
David
Thanks for the thorough explanation David
To help and mentor few developers in team, I wrote this sort of template, I believe this will also help, just sharing !
https://gist.github.com/mailtoharshit/9219539
Thank you Harshit for the extra example!