7

I'm starting out new in APEX. I created below method to call inside a trigger. I know my code below is not efficient calling loop inside a loop to avoid SOQL. I looked online for a day before posting this to look. Any suggestions? I'm not able to figure our how use Maps or Sets to achieve this.

public Static List<Quote> getFactors(List<Quote> lstq){
    List<Margin_Factor__c> lstMarginFactors = [
        SELECT
            Channel__c, Cost__c, Deal_type__c, Margin_type__c, Margin_Unit__c,
            Product_type__c, Range_High__c, Range_low__c, Swing_Type__c
        FROM Margin_Factor__c
    ]; 

    for (Quote q: lstq){
        for (Margin_Factor__c m: lstMarginFactors){
            // Acquisition Cost in $/MWh
            if(m.Channel__c==q.Sales_Channel__c && m.Deal_type__c==q.Enrollment_Type__c && m.Margin_type__c=='COA' && m.Range_low__c <= q.Term_MWh__c && m.Range_high__c > q.Term_MWh__c){
                q.COA_Factor_MWh__c = m.Cost__c;
            }
            // Cost to Serve in $/Month/ESID
            if(m.Channel__c==q.Sales_Channel__c && m.Deal_type__c==q.Enrollment_Type__c && m.Margin_type__c=='CTS' && m.Range_low__c <= q.Term_MWh__c && m.Range_high__c > q.Term_MWh__c){
                q.CTS_Factor_per_Month__c = m.Cost__c;
            }
            // Extra margin in $/MWh
            if(m.Channel__c==q.Sales_Channel__c && m.Deal_type__c==q.Enrollment_Type__c && m.Margin_type__c=='Extra' && m.Range_low__c <= q.Term_MWh__c && m.Range_high__c > q.Term_MWh__c){
                q.Extra_Margin_MWh__c = m.Cost__c;
            }
        }
    }
    return lstq;       
}

There are about 4 fields in common between Quote and Margin_factor__c custom object. And additional 2 fields that define range for a field in Quote. I'm not sure how I can map.

Margin_Factor__c has factors maintained based on common fields as described below:

Common fields between quote and Margin_Factor__c are Channel, Deal_type and Term_MWh__c on quote that fall between Range_high and Range_low in MarginFactor. I have to derive corresponding factors from MarginFactor based on the values from Quote.

  • better you can have Margin_Factor__c in map with Quote as key. Hope this will help you. – Anu Jul 7 '15 at 14:25
  • These cost assignments seem likely to clobber each other frequently. – Adrian Larson Jul 7 '15 at 14:27
  • Thanks for replying Anu. I'm not sure I understand. There are about 4 fields in common between Quote and Margin_factor__c custom object. And additional 2 fields that define range for a field in Quote. I'm not sure how I can map – NewtoApex Jul 7 '15 at 14:28
  • You likely need to use several maps. Use a map for each field that you need to compare with. – dphil Jul 7 '15 at 14:42
  • What's the relationship between quote and marginFactor? I assume they're unique for each quote Id? If so, what would be the WHERE clause in a bulk query? – crmprogdev Jul 7 '15 at 14:48
5

You can use a custom type as your map key here. You'll have to abuse the functionality a bit to make ranges work, but below is a working example.

public class MarginFactorKey
{
    final Decimal low, high;
    final String channel, dealType;
    public MarginFactorKey(String channel, String dealType, Decimal low, Decimal high)
    {
        this.channel = channel;
        this.dealType = dealType;
        this.low = low;
        this.high = high;
    }
    public Integer hashCode()
    {
        return 31 * channel.hashCode() ^ dealType.hashCode();
        // don't hash the range values or you will only get an exact match
        // you'll deal with the range matching below
    }
    public Boolean equals(Object instance)
    {
        MarginFactorKey that = (MarginFactorKey)instance;
        return this.channel == that.channel && this.dealType == that.dealType &&
            that.low <= this.low && that.high >= this.high;
        // first line of Boolean evaluation gives you exact match on String values
        // second line of Boolean evaluation gives you range match on Decimal values 
    }
}

If you're having trouble wrapping your head around this key type, try it with just the Decimal portion or just the String portion. Below is a test method which runs through some input/output combos. It too might help you grok what's going on with this key type.

Map<MarginFactorKey, Integer> demo = new Map<MarginFactorKey, Integer>
{
    new MarginFactorKey('A', 'B', 1, 10) => 5,
    new MarginFactorKey('C', 'D', 1, 10) => 7,
    new MarginFactorKey('M', 'N', 10, 100) => 50,
    new MarginFactorKey('O', 'P', 10, 100) => 75,
    new MarginFactorKey('W', 'X', 100, 1000) => 500,
    new MarginFactorKey('Y', 'Z', 100, 1000) => 750
};

system.assertEquals(5, demo.get(new MarginFactorKey('A', 'B', 2, 2)));
system.assertEquals(7, demo.get(new MarginFactorKey('C', 'D', 3, 9)));
system.assertEquals(50, demo.get(new MarginFactorKey('M', 'N', 12, 42)));
system.assertEquals(75, demo.get(new MarginFactorKey('O', 'P', 25, 75)));
system.assertEquals(500, demo.get(new MarginFactorKey('W', 'X', 200, 250)));
system.assertEquals(750, demo.get(new MarginFactorKey('Y', 'Z', 500, 600)));

system.assertEquals(null, demo.get(new MarginFactorKey('W', 'X', 2, 200)));
system.assertEquals(null, demo.get(new MarginFactorKey('C', 'D', 21, 25)));
system.assertEquals(null, demo.get(new MarginFactorKey('O', 'P', 25, 200)));

I recommend you use the factory pattern to simplify creating these keys, though it is not necessary:

public class MarginFactorKey
{
    // instance methods defined above

    public static MarginFactorKey factory(Quote record)
    {
        return new MarginFactorKey(
            record.Sales_Channel__c, record.Enrollment_Type__c,
            record.Term_MWh__c, record.Term_MWh__c
        );
    }
    public static MarginFactorKey factory(Margin_Factor__c record)
    {
        return new MarginFactorKey(
            record.Channel__c, record.Deal_Type__c,
            record.Range_Low__c, record.Range_high__c
        );
    }
}

You will still need separate maps for each Margin_Type__c, but you will not have to loop through each Margin_Factor__c to find the correct records, if you put all the above together.

public static List<Quote> setFactors(List<Quote> records)
{
    Map<MapKey, Margin_Factor__c> coaFactors = new Map<MapKey, Margin_Factor__c>();
    Map<MapKey, Margin_Factor__c> ctsFactors = new Map<MapKey, Margin_Factor__c>();
    Map<MapKey, Margin_Factor__c> extraFactors = new Map<MapKey, Margin_Factor__c>();
    for (Margin_Factor__c factor : [/*factor query*/])
    {
        if (factor.Margin_Type__c == 'COA')
            coaFactors.put(MarginFactorKey.factory(factor), factor);
        if (factor.Margin_Type__c == 'CTS')
            ctsFactors.put(MarginFactorKey.factory(factor), factor);
        if (factor.Margin_Type__c == 'Extra')
            extraFactors.put(MarginFactorKey.factory(factor), factor);
    }

    // now you can avoid nested loops!

    for (Quote record : records)
    {
        MarginFactorKey key = MarginFactorKey.factory(record);
        if (coaFactors.containsKey(key))
            record.COA_Factor_MWh__c = coaFactors.get(key).Cost__c;
        if (ctsFactors.containsKey(key))
            record.CTS_Factor_per_Month__c = ctsFactors.get(key).Cost__c;
        if (extraFactors.containsKey(key))
            record.Extra_Margin_MWh__c = extraFactors.get(key).Cost__c;
    }
}
  • 1
    +1 for the good use case and use of the word "grok" (learned a new word today) – Eric Jan 22 '17 at 5:44
  • 1
    Wish you could favorite an answer...... – Eric Jan 22 '17 at 16:31
  • @AdrianLarson - Why did you use 37? Just a random number or does the value of 37 have a specific use other than multiplier – Eric Jan 25 '17 at 5:48
  • I think that's the number that they used to have in the documentation (looks like it's 31 now). I don't know that it matters, but multiplying by a prime number might reduce chances of hash collision. – Adrian Larson Jan 25 '17 at 5:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.