0

I have two lists for two different objects. They are both are related to opportunities.

I want to check to see if the two lists are associated with the same opportunity and, if they are associated with the same opportunity, populate a field on one object, with a field from the other object.

    List<abacus__Ledger_Entry__c> ledgList2 = 
             [SELECT abacus__Opportunity__c, abacus__GL_Code__c, 
                     EF_GL_Code__c     
              FROM abacus__Ledger_Entry__c 
              WHERE abacus__Opportunity__c IN :oppIds AND 
                   (abacus__Type__c =: 'Allocation' AND 
                    abacus__Debit_Credit__c =: 'Credit')];
    
    List<npsp__Allocation__c> alloList1 = 
             [SELECT Name, GAU_Account_Structure__c, 
                     npsp__Opportunity__c 
              FROM npsp__Allocation__c 
              WHERE npsp__Opportunity__c IN :oppIds]

I think a map is the best way to do this maybe? But I'm not familiar with implementing those.

The below is the idea of logic I was going to try. I think it is definitely not the right way to go because I have nested for loops.

if(!ledgList2.isEmpty()){
        
 List<abacus__Ledger_Entry__c> updateLedgList2 = new List<abacus__Ledger_Entry__c>();

 List<npsp__Allocation__c> updateAlloList1 = new List<npsp__Allocation__c>();

        for(npsp__Allocation__c allo1 : alloList1){
            for(abacus__Ledger_Entry__c ledg2 : ledgList2){

                ledg2.EF_GL_Code__c = allo1.GAU_Account_Structure__c;
                updateLedgList2.add(ledg2);
        }

        if(!updateLedgList2.isEmpty()){
            update updateLedgList2;

I also am going to run into the issue of there being 1 opportunity with 2 Allocations and 2 Ledger Entries. In this case it would not know which Allocation field to pull my field from. Please let me know if you have any suggestions. Thanks!

3 Answers 3

3

Yep, using a map is the way to go here.

Any time that you find yourself doing something like this

for(item1 :collection 1){
    for(item2 :collection 2){
        if(item1.MyField__c == item2.MyField__c){
            // do things to get data from one record to the other
        }
    }
}

then you should stop and think about using a map instead.

A map is just another type of collection. Instead of .add(), you'd use .put(), and instead of indexing into a list, you use .get().

Continuing with my pseudocode example, the solution using maps would look like this:

// Here, we declare a map where the key is an Id (any Id, though you'll be using
//   Opportunity Ids specficially) and the value is some other object
Map<Id, MyObject__c> oppIdToMyObjMap = new Map<Id, MyObject__c>();

// First loop iterates over one collection and populates the map we just declared (and initialized)
for(MyObject__c item1 :collection1){
    oppIdToMyObjMap.put(item1.Opp__c, item1);
}

// Second loop iterates over the other collection, and sees whether or not we have
//   a matching Opportunity Id in the map
for(OtherObject__c item2 :collection2){
    MyObject__c relatedToSameOpp = oppIdToMyObjMap.get(item2.OppId__c);

    if(relatedToSameOpp != null){
        item2.Field__c = relatedToSameOpp.Field__c;
    }
}

You still have 2 loops, but they're not nested, and (perhaps more importantly) we're not wasting our time running a bunch of IF statements that will turn out to be false (and thus not enter the IF's block).

If you're familiar with Big-O notation, the nested loops approach is O(N*M), where N is the size of collection1, and M is the size of collection 2.
The map approach is O(N+M).

If you're not familiar with Big-O notation, let's say your collection1 has 200 items in it, and your collection2 has 150 items in it.
The nested loops approach would execute the code inside the inner loop 30,000 times.
The map approach would execute code inside of the two loops a total of 350 times (an 85x improvement).

One thing to be aware of

This assumes that your Opportunities are only related to a maximum of 1 of your child object records each.

The moment you have 2 or more records of one of your child objects related to the same opportunity, your problem gets harder.

That's because, with my simple example, the map can only store a single value. The map will end up with whatever value you most recently added for a given key.

Map<Integer, String> intToStrMap = new Map<Integer, String>();
intToStrMap.put(1, 'test');
intToStrMap.put(1, 'foobar');

// This will end up showing you 'foobar' in the debug log
// because that's the last value we put into the map for that key
system.debug(intToStrMap.get(1));

A Map can have its value be another collection, like a Map<Id, List<MyObject__c>>, but then the problem becomes which of these multiple records do you want to use to copy the field value from?

If that's the direction that you're headed in, you should probably take a step back and try to plan out how you'd want to handle that situation (and maybe ask another question after you try to resolve that new issue).

1
  • I really appreciate the info here. The map in theory makes a lot of sense, but just couldn't quite figure out how to implement. Will definitely give it a go. And yes, you're right. One of the reasons I steered away from the map was the fact I realized I would have multiple child records and that put another layer of complexity in.
    – Reed
    Commented Aug 12, 2020 at 19:16
1

One other suggestion I'd add is that if you are mapping fields, you can Custom Metadata to make that more flexible. You can create a new MD object to store the mappings, and then have fields that store the source object\Field and a target object\field. When you use Entity Lookups, selecting the object will then show all the available fields on each object, so makes it easy for an admin to add/manage the field mappings without you needing to change the code More info here: https://help.salesforce.com/articleView?id=custommetadatatypes_relationships.htm&type=5

0

I think you already know this but I'm just laying it out there that it is not advisable to use nested for loops.

Like you said you can use map to compare these two lists. Map<key, value> uses a key value format so you can take the common identifier for the two lists which is opportunity as a key in map to compare.

So your code looks like

List<abacus__Ledger_Entry__c> ledgList2 = [SELECT abacus__Opportunity__c, abacus__GL_Code__c, EF_GL_Code__c  FROM abacus__Ledger_Entry__c WHERE abacus__Opportunity__c IN :oppIds AND (abacus__Type__c =: 'Allocation' AND abacus__Debit_Credit__c =: 'Credit')];



List<npsp__Allocation__c> alloList1 = [SELECT Name, GAU_Account_Structure__c, npsp__Opportunity__c FROM npsp__Allocation__c WHERE npsp__Opportunity__c IN :oppIds]



    Map<id,npsp__Allocation__c> mapalloc = new Map<id,npsp__Allocation__c>();
    
    for(npsp__Allocation__c alloc: alloclist1)
        
    {
        Mapalloc.put(alloc.npsp__Opportunity__c,alloc); // oppkey, full alloc record values
        
    }
    
    for(abacus__Ledger_Entry__c ledg :ledgList2)
        
    {
        If(mapalloc.containskey(ledge.abacus__Opportunity__c)) //checks if map has same opp id
            
        {
            Ledge.EF_GL_Code__c=mapalloc.get(ledge.abacus__Opportunity__c).GAU_Account_Structure__c:
            
            // You can add this to a list and update outside.
     

   }
}

Let me know if you have any questions

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .