How to use Database.Statful in batch apex

apex-codesalesforce

I have a batch process that is aggregating about 275,000 records by fund. I have the batch size set to the maximum number of 200. So, when the batch runs there are 1,380 batches that are processed.

I need to process all the funds by name. For example, if I have a fund called "Fund A". In the first batch of 200 records "Fund A" might be in that batch 10 times and "Fund A" is rolled up for those 10 records and is inserted.

The next batch of 200 records is processed and "Fund A" might be in those 200 records 5 times. "Fund A" is rolled up for those 5 records and inserted.

This continues until all batches have been processed and I end up inserting multiple records for "Fund A".

Reading the batch apex documentation it indicates the following:

Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter is considered five transactions of 200 records each.

If you specify Database.Stateful in the class definition, you can maintain state across these transactions. When using Database.Stateful, only instance member variables retain their values between transactions. Static member variables don’t and are reset between transactions. Maintaining state is useful for counting or summarizing records as they're processed. For example, suppose your job processed opportunity records. You could define a method in execute to aggregate totals of the opportunity amounts as they were processed.

If I understand correctly, I can combine "Fund A" into one record across all batches and then insert that one record?

I'm trying to use database.stateful, but I'm getting the error:

First error: Insert failed. First exception on row 1 with id a28e0000000uvHYAAY; first error: INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call: [Id]

Here is my batch code:

global class ProductionAggregateRollupBatch implements Database.Batchable<sObject>, Database.Stateful {

public string query = 'select DBR__c, FundName__r.Name, FundName__r.Class__c, PurchasesPY__c, PurchasesPYTD__c, PurchasesYTD__c, RedemptionsPY__c, RedemptionsPYTD__c, RedemptionsYTD__c, AUM__c from Production_Aggregate__c';
global Map<String, Funds_Purchased__c> rollupMap = new Map<String, Funds_Purchased__c>();

global database.querylocator start(Database.BatchableContext BC)
{
    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, Sobject[] scope)
{
    Set<Id> dbrIds = new Set<Id>();

    for (Production_Aggregate__c pa : (List<Production_Aggregate__c>)scope) {
        dbrIds.add(pa.DBR__c);
    }

    Map<Id, List<Id>> dbrToContactMap = getDbrToContactMap(dbrIds);

    for (Production_Aggregate__c pa : (List<Production_Aggregate__c>)scope) {
        if(!dbrToContactMap.isEmpty() && dbrToContactMap.size() > 0) {
            if(dbrToContactMap.containsKey(pa.DBR__c)) {
                List<Id> contactIds = dbrToContactMap.get(pa.DBR__c);
                for(Id contactId : contactIds) {

                    String index = '' + new String[] {
                        '' + pa.FundName__r.Name,
                        '' + contactId
                    };                      

                    Funds_Purchased__c fundPurchased = rollupMap.get(index);

                    if(fundPurchased == null) {
                        fundPurchased = new Funds_Purchased__c();
                        rollupMap.put(index, fundPurchased);
                    }

                    fundPurchased.Fund_Name__c = pa.FundName__r.Name;
                    fundPurchased.DBR__c = pa.DBR__c;
                    fundPurchased.Contact__c = contactId;

                    if(pa.AUM__c == null) pa.AUM__c = 0;
                    if(fundPurchased.AUM__c == null)fundPurchased.AUM__c = 0;
                    fundPurchased.AUM__c += pa.AUM__c;

                    if(pa.PurchasesPY__c == null) pa.PurchasesPY__c = 0;
                    if(fundPurchased.PurchasesPY__c == null)fundPurchased.PurchasesPY__c = 0;                       
                    fundPurchased.PurchasesPY__c += pa.PurchasesPY__c;

                    if(pa.PurchasesPYTD__c == null) pa.PurchasesPYTD__c = 0;
                    if(fundPurchased.PurchasesPYTD__c == null)fundPurchased.PurchasesPYTD__c = 0;                   
                    fundPurchased.PurchasesPYTD__c += pa.PurchasesPYTD__c;

                    if(pa.PurchasesYTD__c == null) pa.PurchasesYTD__c = 0;
                    if(fundPurchased.PurchasesYTD__c == null)fundPurchased.PurchasesYTD__c = 0;                     
                    fundPurchased.PurchasesYTD__c += pa.PurchasesYTD__c;

                    if(pa.RedemptionsPY__c == null) pa.RedemptionsPY__c = 0;
                    if(fundPurchased.RedemptionsPY__c == null)fundPurchased.RedemptionsPY__c = 0;                   
                    fundPurchased.RedemptionsPY__c += pa.RedemptionsPY__c;

                    if(pa.RedemptionsPYTD__c == null) pa.RedemptionsPYTD__c = 0;
                    if(fundPurchased.RedemptionsPYTD__c == null)fundPurchased.RedemptionsPYTD__c = 0;                       
                    fundPurchased.RedemptionsPYTD__c += pa.RedemptionsPYTD__c;

                    if(pa.RedemptionsYTD__c == null) pa.RedemptionsYTD__c = 0;
                    if(fundPurchased.RedemptionsYTD__c == null)fundPurchased.RedemptionsYTD__c = 0;                 
                    fundPurchased.RedemptionsYTD__c += pa.RedemptionsYTD__c;                        
                }
            }
        }           
    if(!rollupMap.isEmpty() && rollupMap.size() > 0) {
        insert rollupMap.values();
    }       
}   

global void finish(Database.BatchableContext BC) {

}

private Map<Id, List<Id>> getDbrToContactMap(Set<Id> dbrIds) {
    Map<Id, List<Id>> dbrContactMap = new Map<Id, List<Id>>();
    List<Id> contactIds = new List<Id>();
    for(DBR_Group_Member__c member : [select Id, Contact__c, DBR__c from DBR_Group_Member__c where DBR__c in: dbrIds AND IsActive__c = true]) {
        if(!dbrContactMap.containsKey(member.DBR__c)) {
            contactIds = new List<Id>();
            dbrContactMap.put(member.DBR__c, contactIds);
        }
        contactIds.add(member.Contact__c);
    }
    return dbrContactMap;
}
}

Thanks for any help. Anyone?

Best Answer

Move your insert to the finish method:

global void finish(Database.BatchableContext BC) {
    if(rollupMap.size() > 0) {
        insert rollupMap.values();
    }
}

Your code had insert at the end of the execute method and would attempt to call it 1380 times. On the first execute insert probably works and assigns Ids back to your in-memory objects. On the second execute your insert attempt throws the error message that you can't insert with given Ids. Moving insert to finish() will ensure you only insert once after all of your execute iterations are complete.

Related Topic