I'm trying to figure out how best to modify an existing Apex class to implement the Database.Batchable interface. I currently have two classes:

CustomerInfoUpdater - this class is void and is currently a Scheduled Job in our Sandbox via the "Schedule Apex" gui screen. The logic performs a MAX function to get the highest value in the Latest_Year_of_Training__c field on the contact object, and then update the corresponding customer info record with the same AccountId. There are two objects that are both master-detail to an Account that are used in this class: Contacts - standard JBCXM__CustomerInfo__c - this is a custom object that is used in a 3rd party system. Account records have 1 customer info record, and contains summary level detail for a given account. ScheduleCustomerInfoUpdater - this is a utility class that sets up CustomerInfoUpdater as a scheduled job. The CRON details are not in this class - I'd like to have the schedule set in the Scheduled Jobs gui in Salesforce. It came to my attention that since I'm dealing with bulk records it makes the most sense to use the Database.Batchable interface, which will also make it easier to develop a test class. I understand that Database.Batchable requires the 3 methods of start, execute, and finish, but I'm uncertain how to take my existing code and align it to the requirements of the interface.

Can someone please take a look at my existing code and recommend what would need to be done in order to implement Database.Batchable? Any help is greatly appreciated.

public class CustomerInfoUpdater {
   public static void updateTrainingYear() {
      // get full list of contacts who have attended training sessions
      List < AggregateResult > attendeeAccounts = [SELECT AccountId, MAX(Latest_Year_of_Training_Attended__c) FROM Contact WHERE Latest_Year_of_Training_Attended__c != NULL GROUP BY AccountId];
      System.debug('There are ' + attendeeAccounts.size() + ' accounts in the list.');

      // parse the attendeeAccounts aggregate result into a usable map
      Map < Id, Double > accountTrainingYear = new Map < Id, DOUBLE > ();
      for (AggregateResult aR: attendeeAccounts) {
       accountTrainingYear.put((ID) aR.get('AccountId'), (DOUBLE) aR.get('expr0'));

      // get all customer info records where the account IDs match
      List < JBCXM__CustomerInfo__c > customerInfoList = [SELECT Id,
       FROM JBCXM__CustomerInfo__c
       WHERE JBCXM__Account__c IN: accountTrainingYear.keySet()

      System.debug('There are ' + customerInfoList.size() + ' customer info records in the list.');

      // holding list for records to update
      List < JBCXM__CustomerInfo__c > customerInfoUpdateList = new List < JBCXM__CustomerInfo__c > ();

      // iterate through the list of customer info records and set the product training year 
      for (JBCXM__CustomerInfo__c cI: customerInfoList) {
      cI.Product_Training_Attendance__c = accountTrainingYear.get(cI.JBCXM__Account__c);
      System.debug('Setting Customer Info Record: ' + cI.Id + ' against AccountId: ' + cI.JBCXM__Account__c + 'to ' + accountTrainingYear.get(cI.JBCXM__Account__c));

      // DML update to adjust customer info records
      update customerInfoUpdateList;

global class scheduleCustomerInfoUpdater implements Schedulable {
    global void execute(SchedulableContext SC) {

1 Answer 1


OK so for every Account with Contacts, you find the latest training year on any of its Contacts. Then you find all Customer Info records for those Accounts. You iterate through those and store the training attendance year accordingly.

Incidentally if you have Declarative Lookup Rollup Summaries in your org (DLRS), you could simply set a declarative rollup to find that max year on Contacts and roll it up to an Account field. This could be done real-time or scheduled. Once that value is available on Account, your Customer Info objects can find it through the parent field (or you can use a formula field, or you can have a Process Builder keep child Customer Info records stamped with the right value). "Code-free" solution!

If you do want to go with Apex and Database.Batchable you just need to decide which query to set up in your start method. If you iterate over the AggregateResult query, then your execute method will see 200 of those. You can continue with the rest of your code pretty much as-is, and at the end of the execute method you update the relevant Customer Info records. You could also just as easily start by querying the Customer Info records and work the other way.

The finish method can be blank if you don't need it.

  • DLRS ended up being the easiest way to accomplish what I needed. Thanks again Charles!
    – Tom Kline
    Mar 13, 2017 at 13:50
  • Welcome! It's definitely useful for all sorts of situations where you need to pull data up by a level to give visibility from other objects.
    – Charles T
    Mar 13, 2017 at 18:32

You must log in to answer this question.

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