function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
AsokAsok 

How to use Database.Stateful in batch Apex?

Dear All,

 

The problem I am facing is, I have batch apex code updating two fields in Opportunity line Item(OLI). While I run the code for complete batch the values in the field are updating correctly. However, If I spilt the batch apex in two or many, I am facing the issue of the last batch value getting updated in Opportunity line item instead of complete one.

 

for ex:- Consider 3 record of value 50 each, I am expecting the field in OLI to be updated by 150 and it is updating rightly while I run the batch for  Database.executeBatch(job,3); where as if I split the same as  Database.executeBatch(job,2); then the field is getting updated by 50 instead of 150.

 

I read, Database.Stateful can be used to maintain the state accross the batch, can someone help me with sample code for Database.stateful illustrating how to use the function.

 

Thanks in Advance!

Best Answer chosen by Admin (Salesforce Developers) 
BritishBoyinDCBritishBoyinDC

Simple example - we want to add up the sum of all opportunities for each Account and store it in a field on Account called test_amount__c - and for some reason we can't use a rollup.

 

So we build a batch that receives all opportunities withn an Account and Amount, and keeps a running total for each account using an account map, and finally update the accounts in the final part of the batch:

 

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

   global final String query;
   global Map<Id, Account> accountmap;
  
   global SummarizeAccountTotal(){
   accountmap = new Map<Id, Account> ();
   }

   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }
   
   global void execute(Database.BatchableContext BC, List<sObject> scope){
   
   List<Opportunity> ops = (List<Opportunity>)scope;
   
   for (Opportunity o : ops) {
   
   if (accountmap.containskey(o.AccountId)) {
   Account a = accountmap.get(o.AccountId);
   a.Test_Amount__c += o.Amount;
   accountmap.put(o.AccountId, a);
   }
   else{
   accountmap.put(o.AccountId, new Account (Id = o.AccountId, Test_Amount__c = o.Amount));
   } 
         
   }
   }
   

global void finish(Database.BatchableContext BC){
   
   try {
   update accountmap.values();
   }
   catch (Exception Ex) {
   system.debug(Ex);
   }
   
   }
}

 

You can call it with this code:

 

SummarizeAccountTotal sat = new SummarizeAccountTotal ();

sat.query = 'Select Id, AccountId, Amount from Opportunity where AccountId != null and Amount > 0';

database.executebatch(sat);

 

 

All Answers

bob_buzzardbob_buzzard

While database.stateful may well be the route you need to go, it sounds like you are overwriting the value in the OLI rather than adding the value that your batch has calculated.  Is that your intention?

AsokAsok

Yes, Exactly. I am trying to add up the batch values calculated instead of overwriting the values. Please help me with a piece of sample code if you have any.

 

Thanks in Advance!

 

Regards,

SRA

bob_buzzardbob_buzzard

Can you post your code?

 

 

AsokAsok

Sorry Bob, won't be able to post the code...If you have any generic code to help out me, that would be great!

bob_buzzardbob_buzzard

I'm afraid not.  However, there's an example in the Apex Developer's Guide at:

 

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm

 

about 2/3 of the way down.

BritishBoyinDCBritishBoyinDC

Bob's right - the example from the docs below should work for you - but instead of just using an integer, you can use a Map to keep track of each opportunity and it's running total, then loop through the map in the final method and update the Opportunities...

 

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

   global final String Query;
   global integer Summary;
  
   global SummarizeAccountTotal(String q){Query=q;
     Summary = 0;
   }

   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }
   
   global void execute(Database.BatchableContext BC, List<sObject> scope){
      for(sObject s : scope){Summary = Integer.valueOf(s.get('total__c'))+Summary;
      }
   }

global void finish(Database.BatchableContext BC){
   }
}

 

 

AsokAsok

Can you please illustrate me on using Map for the integer, as you suggested?

 

BritishBoyinDCBritishBoyinDC

Simple example - we want to add up the sum of all opportunities for each Account and store it in a field on Account called test_amount__c - and for some reason we can't use a rollup.

 

So we build a batch that receives all opportunities withn an Account and Amount, and keeps a running total for each account using an account map, and finally update the accounts in the final part of the batch:

 

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

   global final String query;
   global Map<Id, Account> accountmap;
  
   global SummarizeAccountTotal(){
   accountmap = new Map<Id, Account> ();
   }

   global Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }
   
   global void execute(Database.BatchableContext BC, List<sObject> scope){
   
   List<Opportunity> ops = (List<Opportunity>)scope;
   
   for (Opportunity o : ops) {
   
   if (accountmap.containskey(o.AccountId)) {
   Account a = accountmap.get(o.AccountId);
   a.Test_Amount__c += o.Amount;
   accountmap.put(o.AccountId, a);
   }
   else{
   accountmap.put(o.AccountId, new Account (Id = o.AccountId, Test_Amount__c = o.Amount));
   } 
         
   }
   }
   

global void finish(Database.BatchableContext BC){
   
   try {
   update accountmap.values();
   }
   catch (Exception Ex) {
   system.debug(Ex);
   }
   
   }
}

 

You can call it with this code:

 

SummarizeAccountTotal sat = new SummarizeAccountTotal ();

sat.query = 'Select Id, AccountId, Amount from Opportunity where AccountId != null and Amount > 0';

database.executebatch(sat);

 

 

This was selected as the best answer
symantecAPsymantecAP

I am running in a similar kind of situation, Any help will be greatly appreciated.

 

I have Managed object called BIG MACHINE QUOTES. the req is if the Status field on Big Machine Quotes contains Unison then the Parent Object Opportunity field Stage should be Closed Won. and this batch should be scheduled every 15 min .

Here is my code so far.

 

Please help me on this

global class updateOpportunityStage implements Database.Batchable<sObject>,Schedulable{
global string query ;

global updateOpportunityStage(){

Query = 'Select Id,BigMachines__Status__c  from BigMachines__Quote__c' ;

}

global database.querylocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);    
}
    global void execute(SchedulableContext SC){
        updateOpportunityStage stg = new updateOpportunityStage();
        //String SCHEDULE_NAME = 'Process Quotes'; 
        //id cronid = System.schedule(SCHEDULE_NAME, '0 0 * * * ?', stg); 
        
       String sch1 = '0 0 * * * ?';
System.schedule('Schedule Job1', sch1,stg);

  updateOpportunityStage stg2 = new updateOpportunityStage();
String sch2 = '0 15 * * * ?';
System.schedule('Schedule Job2', sch2, stg2);

        updateOpportunityStage stg3 = new updateOpportunityStage();
String sch3= '0 30 * * * ?';
System.schedule('Schedule Job3', sch3, stg3);

        updateOpportunityStage stg4 = new updateOpportunityStage();
String sch4 = '0 45 * * * ?';
System.schedule('Schedule Job4', sch4, stg4);

        
        
        
      //  System.abortJob(cronid);
//String cronStr =Datetime.now().addSeconds(10).format('s m H d M ? yyyy');
     //  System.schedule('Process Quotes', cronStr, stg);
        //System.abortJob(cronstr);
        
       /* String hour = String.valueOf(Datetime.now().hour());

String min = String.valueOf(Datetime.now().minute());

String ss = String.valueOf(Datetime.now().second());

String nextFireTime = ss + ' ' + min + ' ' + hour + ' * * ?';

system.schedule('Start me once', nextFireTime, stg); */
        database.executebatch(stg);
        
    }

global void execute(Database.BatchableContext BC, List<sObject> scope){

     
        Set<id> liOppIds = new Set<id>();
//List <Opportunity> oppList = new List<Opportunity>() ;
for(sObject s : scope){

BigMachines__Quote__c quote = (BigMachines__Quote__c)s;
System.debug('Adil'+quote);
if(quote.BigMachines__Status__c == '*unison*' && quote.BigMachines__Is_Primary__c == true)
liOppIds.add(quote.BigMachines__Opportunity__c);

}


//query all the opportunities in a single query
List<Opportunity> opp = new List<Opportunity>();
opp = [select id, StageName from Opportunity where id in :liOppIds and stagename != 'Closed Won'];
for ( Opportunity opps : opp)
{
opps.StageName = 'Closed Won' ; 
}
//update all opportunities in a single DML
if(opp.size() > 0)
update opp;
 
    }
  global void finish(Database.BatchableContext BC){}  

}

 Thanks

Adil

 

AsokAsok

Hi Adhil,

 

If the problem, is that the code is not  maintaining the state then use the Keyword 'Database.Stateful' while initiating the batch job.

 

Also, to maintain the the state across the batches, set the list, under global string query declaration.

List<Opportunity> opp = new List<Opportunity>();

 

This will not empty the list everytime. Hope this helps. Let me know if this is useful!

Cheers,

SRA

tggagnetggagne

I simply want to keep track of the number of records processed so it may be included in the email at the end.

 

global class UpdateCustomer implements Database.Batchable<CustomerCollection>, Database.Stateful {
	global class CustomerCollection {
		/* not important */
	}
	
	global class CustomerIterable implements Iterable<CustomerCollection>
	{
		global Iterator<CustomerCollection> Iterator() {
			return new CustomerIterator();
		}
	}
	
	global class CustomerIterator implements Iterator<CustomerCollection> {
		/* not important */
	}
	
	global void execute(Database.BatchableContext bc, list<CustomerCollection> aList) 
	{
		list<Account_Relationship__c> customerList = new list<Account_Relationship__c>();
		
		for (CustomerCollection each : aList) {
                        recordCount += each.size();
			// single customer -- no complications
			if (each.size() == 1) {
				customerList.add(accountRelationshipFromStage(each.customerList[0]));
			}			
		}
		
		upsert customerList;
	}
	
	global void finish(Database.BatchableContext bc){
		system.assertNotEquals(0, recordCount);
	}
	
	global Iterable<CustomerCollection> start(Database.BatchableContext bc)  
	{
		return new CustomerIterable();
	}
	
	global UpdateCustomer() { recordCount = 0; }
	
	global integer recordCount;
}

 Why does recordCount not get updated, or if it is, why is it back to zero inside finish()?

tggagnetggagne

Never mind.  I knew my batches were failing on the upsert (my trigger calls an @future method), but it wouldn't have occurred to me the state wouldn't have been updated.

 

All good.