[SalesForce] How to prevent an upsert from overwriting fields with existing values with null

I have an object that I update overnight by getting data from an external system. In the callout I cast the data into my object and do an UPSERT with external ID. It all works correctly apart from one small issue.

If a Salesforce user added some text to Description__c field before the callout, the upsert will overwite this field with null since this value doesn't exist in the other system.

Is there any way for me to only update the fields that have no values or fields where both new and old have values with an upsert call?

The only thing that comes to mind is to query for those records that i'm trying to update and then pick and chose which fields to update.

Maybe there's a better way? What is the best practice in this regard?

Best Answer

The underlying issue here is that, no matter what your approach is, you have to somehow deal with the fact that Description__c is being set on the SObject instance you're using to upsert.

Even if it's being set to null, it still counts as the field being set/populated. All populated fields in an update/upsert will overwrite the current values stored in Salesforce.

If it's possible for you to simply not set Description__c when you convert your external data to an SObject instance, that'd probably be the easiest and least resource-intensive approach.

Beyond that, I think the only approach I can come up with involves creating a new in-memory instance of your SObject and using getPopulatedFieldsAsMap().

If you simply want to disregard a specific set of fields, something like the following should do the job

// I'm assuming that externalAcctData is properly set elsewhere in the code
Account externalAcctData; 

Account acctCopy = new Account();

// Define which fields we want to _never_ update
Set<String> skipCopyOfField = new Set<String>{'Description', 'MyCustomField__c'};


// getPopulatedFieldsAsMap() does what it says on the tin
// The keyset of that map contains the api name of the field, which we can then use
//   in the SObject put() and get() methods
for(String field :externalAcctData.getPopulatedFieldsAsMap().keySet()){
    if(!skipCopyOfField.contains(field)){
        acctCopy.put(field, externalAcctData.get(field));
    }
}

// Now that we've created an instance of our SObject that does not have our target
//   field(s) (the one(s) whose value(s) we want to preserve in the stored data in SFDC)
//   set, we can use it to do our update/upsert instead of using externalAcctData
upsert acctCopy;

This is necessary because Salesforce does not provide us with a way to unset/un-populate a single SObject field at time of writing (Spring '20, API v48.0).

Related Topic