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
Alex MezaAlex Meza 

Converting a trigger into a batch class.

I wrote an apex trigger that connected two of my objects (Contacts and Voter File-custom object) via a look up field if they had the same first name, last name, and zipcode. 

I was able to succesfully to this however, once I deployed to production I found out that my query was to large as I was trying to match across 15 million records or so. 

After looking into I found out that I had to write an apex Batch class in order to have this work succesfully with my 15 million plus records, however now I am stuck.

I tried looking up some articles but I am not really sure what way to go such as if I have to start all over or just write the batch class based off the trigger I already wrote. 

The trigger is below if you guys could help me with this I would be able to write the classes for my other triggers. 

Thanks. 
 
trigger Contact2VoterID on Contact (before insert,before update,after insert,after update) 
{
{
    Set<String> set_Str = new Set<string>();
    Map<String,Voter_File_TX__c> mp_VoterFile;
    
    if(Trigger.isAfter && Trigger.isUpdate)
    {
        for(Voter_File_TX__c VoterFile : [Select ID,First_Name__c, Last_Name__c,Zipcode__c, Contact__c From Voter_File_TX__c])
        {
                    if(mp_VoterFile==null)
                        mp_VoterFile = new Map<String,Voter_File_TX__c>();
            
            mp_VoterFile.put(VoterFile.First_Name__c +''+VoterFile.Last_Name__c +''+VoterFile.Zipcode__c,VoterFile);

 
                    }
        
        for(Contact ContactList : Trigger.new)
    {
        if(mp_VoterFile!=null && mp_VoterFile.containsKey(ContactList.FirstName+''+ ContactList.LastName+''+ ContactList.MailingPostalCode))

        {

       mp_VoterFile.get(ContactList.FirstName +''+ ContactList.LastName +''+ ContactList.MailingPostalCode).Contact__c = ContactList.id;

}

}
 
if(mp_VoterFile!=null && mp_VoterFile.values()!=null)
        update mp_VoterFile.values();

    }
}
}



 
Best Answer chosen by Alex Meza
Ahmad J. KoubeissyAhmad J. Koubeissy
Hi alex, this the converted code ( from trigger to Apex Class)
global class UpdateContactsVoter implements Database.Batchable<sObject>{
    global Database.QueryLocator start(Database.BatchableContext BC){
        String query = 'Select ID from contact'; // write the query to select only the contacts that you want to update
        return Database.getQueryLocator(query);
    }
    
    global void execute(Database.BatchableContext info, List<contact> scope){
        Set<String> set_Str = new Set<string>();
		Map<String,Voter_File_TX__c> mp_VoterFile;

        for(Voter_File_TX__c VoterFile : [Select ID,First_Name__c, Last_Name__c,Zipcode__c, Contact__c From Voter_File_TX__c] ){
			if(mp_VoterFile==null){
				mp_VoterFile = new Map<String,Voter_File_TX__c>();
			}
			mp_VoterFile.put(VoterFile.First_Name__c +''+VoterFile.Last_Name__c +''+VoterFile.Zipcode__c,VoterFile);
		}
		for(Contact ContactList : scope){
			if(mp_VoterFile!=null && mp_VoterFile.containsKey(ContactList.FirstName+''+ ContactList.LastName+''+ ContactList.MailingPostalCode)){
				mp_VoterFile.get(ContactList.FirstName +''+ ContactList.LastName +''+ ContactList.MailingPostalCode).Contact__c = ContactList.id;
			}
		}
		if(mp_VoterFile!=null && mp_VoterFile.values()!=null){
			update mp_VoterFile.values();
		}
    }
    
    global void finish(Database.BatchableContext info){
        
    }
}
to call this batch from Developer Console (Execute Anonymous):
UpdateContactsVoter batch = new UpdateContactsVoter ();
Database.executeBatch(batch, 200);

or you can schedule it.

Dont hesitate to contact me if you have any question.

Best Regards.

All Answers

LBKLBK
hi Alex,

Trigger would be the ideal solution for you, however, you have to figure out ways to reduce the size of the dataset you have to work with.

For example, in the code below, I have achieved dataset size reduction by following two simple steps.

1. Filtered down the Voter_File_TX__c dataset by filtering the records using the MailingPostalCode in trigger.new.

2. Instead of updating mp_VoterFile (which contains quite a lot of records), I have taken the updated Voter_File_TX__c records into a smaller list.
trigger Contact2VoterID on Contact (after insert,after update)
{
    Map<String,Voter_File_TX__c> mp_VoterFile;
    List<String> lstZipCodes = new List<String>();
    List<Voter_File_TX__c> lstVoterToBeUpdated = new List<Voter_File_TX__c>();
    if(Trigger.isAfter && Trigger.isUpdate){
        for(Contact cnct : trigger.new){
            if(cnct.MailingPostalCode != null && cnct.MailingPostalCode != ''){
                lstZipCodes.add((String)cnct.MailingPostalCode);
            }
        }
        for(Voter_File_TX__c VoterFile : [Select ID,First_Name__c, Last_Name__c,Zipcode__c, Contact__c From Voter_File_TX__c WHERE Zipcode__c = :lstZipCodes]){
            if(mp_VoterFile==null)
            mp_VoterFile = new Map<String,Voter_File_TX__c>();

            mp_VoterFile.put(VoterFile.First_Name__c +''+VoterFile.Last_Name__c +''+VoterFile.Zipcode__c,VoterFile);
        }
        if(mp_VoterFile!=null){
            for(Contact ContactList : Trigger.new){
                if(mp_VoterFile.containsKey(ContactList.FirstName+''+ ContactList.LastName+''+ ContactList.MailingPostalCode))
                {
                    Voter_File_TX__c objVoterFile = mp_VoterFile.get(ContactList.FirstName +''+ ContactList.LastName +''+ ContactList.MailingPostalCode);
                    objVoterFile.Contact__c = ContactList.id;
                    lstVoterToBeUpdated.add(objVoterFile);
                }
            }
        }
        if(lstVoterToBeUpdated.size() > 0){
            update lstVoterToBeUpdated;
        }        
    }
}
Hope this helps.
Feel free to improve the code further.
Ahmad J. KoubeissyAhmad J. Koubeissy
Hi alex, this the converted code ( from trigger to Apex Class)
global class UpdateContactsVoter implements Database.Batchable<sObject>{
    global Database.QueryLocator start(Database.BatchableContext BC){
        String query = 'Select ID from contact'; // write the query to select only the contacts that you want to update
        return Database.getQueryLocator(query);
    }
    
    global void execute(Database.BatchableContext info, List<contact> scope){
        Set<String> set_Str = new Set<string>();
		Map<String,Voter_File_TX__c> mp_VoterFile;

        for(Voter_File_TX__c VoterFile : [Select ID,First_Name__c, Last_Name__c,Zipcode__c, Contact__c From Voter_File_TX__c] ){
			if(mp_VoterFile==null){
				mp_VoterFile = new Map<String,Voter_File_TX__c>();
			}
			mp_VoterFile.put(VoterFile.First_Name__c +''+VoterFile.Last_Name__c +''+VoterFile.Zipcode__c,VoterFile);
		}
		for(Contact ContactList : scope){
			if(mp_VoterFile!=null && mp_VoterFile.containsKey(ContactList.FirstName+''+ ContactList.LastName+''+ ContactList.MailingPostalCode)){
				mp_VoterFile.get(ContactList.FirstName +''+ ContactList.LastName +''+ ContactList.MailingPostalCode).Contact__c = ContactList.id;
			}
		}
		if(mp_VoterFile!=null && mp_VoterFile.values()!=null){
			update mp_VoterFile.values();
		}
    }
    
    global void finish(Database.BatchableContext info){
        
    }
}
to call this batch from Developer Console (Execute Anonymous):
UpdateContactsVoter batch = new UpdateContactsVoter ();
Database.executeBatch(batch, 200);

or you can schedule it.

Dont hesitate to contact me if you have any question.

Best Regards.
This was selected as the best answer
Alex MezaAlex Meza
Hey, Ahmad J. Koubeissy thanks for the the help on this however I am still running into trouble when i try to execute this class in the Developer Console. 

It says there is an issue with line 18 i got this error. 

Failed to process batch for class 'UpdateContactsVoter' for job id '7076300000IyLT1'
 
caused by: System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Contact.FirstName
 
Class.UpdateContactsVoter.execute: line 18, column 1


Not sure where the issue is here because this is code worked in my trigger i first posted, do you have any thoughts on this?
 
LBKLBK
Hi Alex,

If you observe Ahmad's code, he left the SOQL query that fetches Contacts, open ended, for you to update.

Please update line 3 of the code in such a way that it will fetch all the required fields from Contacts.

Please change
String query = 'Select ID from contact';
to something like this...
String query = 'Select ID, FirstName, LastName, MailingPostalCode from contact';
This will ensure that the error doesn't show up.

However, please make sure you put the right WHERE clause to restrict it from fetching all the records unnecessarily.

Hope this helps.
Ahmad J. KoubeissyAhmad J. Koubeissy
hey Alex,

as LBK said, you should update the query and select the fields that you want to use, like firstname and lastname and any other field. And as he said dont forget to limit the query by using wehere clause so you only update the contacts that you want to update.

Please mark my previous answer as solution by selecting it as best answer if this solves your problem, So that if anyone has this issue this post can help.
Subhasmita Bhandari 24Subhasmita Bhandari 24
Hi, I am facing a similar issue. I wrote an apex that connected two custom objects (abc and xyz) to find out the non identical xyz records from millions of abc records and add those new entries. For this I would be needing a batch class to run monthly.
Please help me to write a batch apex in order to have this code work for thousands of records.



trigger StageABC on abc__c (after insert) {

    List<String> storeName = new List<String>();
    List<String> storeNumber = new List<String>();

    for (abc__c sABC : trigger.new) {
        storeName.add(sABC.Store__c);
        storeNumber.add(sABC.Store_Number__c);
    }

    List<xyz__c> xyzLoc = [
        SELECT Id, ZipCode__c,Status__c,Store_Number__c,Store__c, FROM xyz__c
        WHERE Store__c IN: storeName 
        AND Store_Number__c IN: storeNumber
    ];

    Set<String> uniqueSet = new Set<String>();

    for(xyz__c loc:  xyzs) {
        uniqueSet.add(loc.Store__c+ loc.Store_Number__c);
    }

    List<xyz__c> insertXYZ = new List<xyz__c>();
    
    for (Staging__c sABC : trigger.new) {
        if (!uniqueSet.contains(sABC.Store__c + sABC.Store_Number__c)) {

            xyz__c loc = new xyz__c();
            loc.Store__c = sABC.Store__c;
            loc.Store_Number__c = sABC.Store_Number__c;
    loc.ZipCode__c = sABC.ZipCode__c;
    loc.Status__c = 'Active';
        
            insertXYZ.add(loc);
        } 
    }

    if (! insertXYZ.isEmpty()) {
        insert insertXYZ;
    }
}