Salesforce coding lessons for the 99%
Finally, Apex tutorials for point-and-click admins! Written by a self-taught Google engineer.
  • Beginner Tutorials
    • Apex
    • Certifications
    • Career Info
    • Technical Architect
    • Visualforce
    • Videos
  • Apex Academy
  • Success Stories
  • About Me
  • Misc
    • Mailbag
    • Challenges
    • Links
    • Login to my Org
Follow @dvdkliuor SUBSCRIBE!

Comparing old and new values in a trigger

February 25, 2014

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:

  • When an opportunity changes to “Closed Won”
  • When a record owner is changed to somebody else
  • When a field is populated for the first time

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!

60 Comments
Vatsal
January 28, 2020 @ 6:39 pm

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

Reply
    Nate
    February 10, 2020 @ 12:30 pm

    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.

    Reply
      john
      September 10, 2020 @ 2:22 am

      to make it more simple, you could use the Boolean isWon. In that case, vatsal suggestion would work like it show.

      Reply
        Stephen
        October 28, 2020 @ 12:01 pm

        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!

        Reply
      Arunima
      May 18, 2022 @ 6:47 am

      Hi Nate ,

      Could you tell how it should be written . I am new to maps

      Regards,
      Arunima

      Reply
Kris
August 8, 2018 @ 9:03 am

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

Reply
    anjusha
    September 14, 2018 @ 6:24 am

    hi kris,
    you can use triggers to update unrelated objects.bt what u meant by “linked together by account id and category”?

    Reply
      Deepthi
      May 27, 2021 @ 8:21 am

      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.

      Reply
Miranda Baghshvili
May 22, 2018 @ 4:27 pm

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-

Reply
    David Liu
    May 22, 2018 @ 8:42 pm

    Hey Miranda hope you’ve been well!!

    Check out this post!
    https://www.sfdc99.com/2013/10/12/comparison-operators/

    Reply
harish
March 26, 2018 @ 11:09 pm

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

Reply
Kishore
August 1, 2017 @ 8:40 am

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;
}
}
}

Reply
    PikachuPancakes
    October 1, 2017 @ 7:07 am

    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.

    Reply
Shahroz
April 14, 2017 @ 11:23 pm

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 ?

Reply
    David Liu
    April 15, 2017 @ 1:33 pm

    That’s a lot of records! Consider using batch Apex and/or summary objects!

    Reply
Apoorv jain
August 25, 2016 @ 11:59 pm

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’

Reply
    krishan
    October 27, 2016 @ 1:10 am

    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’);”

    Reply
Amrinder
March 18, 2016 @ 7:15 am

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.

Reply
Jolie Tan
August 13, 2015 @ 3:04 pm

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

Reply
    David Liu
    August 13, 2015 @ 7:22 pm

    Ditch the Trigger.old and compare object1.field__c == object2.field__c

    Reply
Shalini
April 6, 2015 @ 8:49 pm

You are awesome!

Reply
    Ahmed Kamran
    June 25, 2015 @ 12:47 am

    Great!

    Reply
Chris
February 12, 2015 @ 11:29 am

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?

Reply
    David Liu
    February 12, 2015 @ 6:29 pm

    Try updating another field to see if it works there =)

    Reply
Chris
February 10, 2015 @ 11:39 am

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

Reply
    David Liu
    February 10, 2015 @ 8:59 pm

    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)

    Reply
      Josh
      February 10, 2015 @ 10:16 pm

      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

      Reply
        David Liu
        February 11, 2015 @ 12:02 am

        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

        Reply
          Josh
          February 11, 2015 @ 12:51 am

          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

          Reply
            Josh
            February 11, 2015 @ 12:59 am

            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

            Reply
rajkumargaikwad
February 6, 2015 @ 5:46 am

Great Lesson David…Enjoying …Learning……thanks a lot !! :)

Reply
Anonymous
December 4, 2014 @ 11:35 am

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

Reply
    David Liu
    December 4, 2014 @ 9:39 pm

    You’ll need this post, plus SOQL in your trigger =)

    Give it a shot and post it on our forums!

    Reply
Matt Mitchener
November 21, 2014 @ 8:09 am

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

Reply
    David Liu
    November 22, 2014 @ 4:49 pm

    Thanks for the info Matt!

    Reply
Lutgardo García Escalera
September 23, 2014 @ 12:46 pm

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?

Reply
Ronnie Thomas
September 11, 2014 @ 4:21 pm

How would you write a test class for a trigger like the one in the example above?

Reply
    Ronnie Thomas
    September 11, 2014 @ 4:32 pm

    Never mind David I was able to think through it and figure it out on my own! I am so proud of myself!!!!!

    Reply
      David Liu
      September 11, 2014 @ 11:04 pm

      Proud of you too Ronnie!

      Reply
      sindhu
      April 27, 2017 @ 7:21 pm

      can you please share text class

      Reply
Felix N
June 17, 2014 @ 7:08 pm

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.

Reply
    David Liu
    June 17, 2014 @ 8:52 pm

    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

    Reply
Viru
June 11, 2014 @ 4:02 am

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

Reply
    David Liu
    June 11, 2014 @ 5:16 pm

    Sweet! Happy to see you learning Viru!!

    Reply
Josh E
May 25, 2014 @ 12:21 am

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

Reply
    David Liu
    May 25, 2014 @ 8:52 am

    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

    Reply
      Josh E
      June 1, 2014 @ 1:38 am

      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

      Reply
        David Liu
        June 1, 2014 @ 7:21 pm

        100% true! One equals (=) will set a value while .equals or two equals (==) will do a true/false comparison!

        Reply
          Harish
          July 1, 2014 @ 11:39 pm

          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

          Reply
            David Liu
            July 1, 2014 @ 11:57 pm

            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!

            Reply
Anonymous
March 21, 2014 @ 8:06 pm

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.

Reply
    David Liu
    March 21, 2014 @ 8:14 pm

    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

    Reply
      Anonymous
      March 22, 2014 @ 6:21 am

      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!

      Reply
      Amit
      April 1, 2014 @ 4:29 am

      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

      Reply
        David Liu
        April 1, 2014 @ 8:06 am

        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

        Reply
Brian
March 3, 2014 @ 6:58 am

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

Reply
    David Liu
    March 3, 2014 @ 10:35 pm

    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

    Reply
      Anonymous
      March 4, 2014 @ 12:05 pm

      Thanks for the thorough explanation David

      Reply
Harshit Pandey (@OyeCode)
February 25, 2014 @ 11:31 pm

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

Reply
    David Liu
    February 25, 2014 @ 11:46 pm

    Thank you Harshit for the extra example!

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *


*

*

Theme: Simple Style by Fimply