1

I have a batch APEX class that is used to delete any contacts that have been created over a year ago and have no related opportunities. I have queries for the contacts in the QueryLocator. I am then querying for all opportunities in the execute however am hitting limits. Can anyone suggest how I can query all opportunities without errors?

Batch Class

global class DeleteContact implements Database.Batchable<sObject>{

global date d = system.today().addYears(-1).addDays(-3);
public String query = 'select id, opportunity__c, RecordTypeId, email, customer_Number__c from contact where createddate <= :d';

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

global void execute(Database.BatchableContext BC, List<Contact> scope){
    Map<Id, Id> oppMap = new Map<Id, Id>();
    Map<Id, Contact> contactMap = new Map<Id, Contact>();
    List<Contact> contactsInScope = new List<Contact>();

    for(Contact c: scope){
        if(!contactMap.containsKey(c.id)){
            contactMap.put(c.id, c);
        }
    }



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

    opportunities = [select id, contact__c, stagename from opportunity where contact__c != ''];

    for(Opportunity o : opportunities){
        if(contactMap.containskey(o.Contact__c) ){
            oppMap.put(o.Contact__c, o.id);
        }
    }

    for(Contact con : scope){
        if(!oppMap.containsKey(con.id)){
            contactsInScope.add(con);
        }
    }

    Id b2cId = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('B2C Contact').getRecordTypeId();

    List<Contact> contactsToDelete = new List<Contact>();

    for(Contact co : contactsInScope){
            if(co.RecordTypeId == b2cId){
                if(co.Customer_Number__c == ''){
                    if(!co.email.contains('@***emaildomain.co.uk')){
                        contactsToDelete.add(co);
                    }
                }
            }
    }
    if(contactsToDelete.size()>0){
        try{
            delete contactsToDelete;
        }
        catch(Exception e){}
    }

}
global void finish(Database.BatchableContext BC){

}

}

0

2 Answers 2

9

I have few recommendation regarding your code:

  • Make query selective. [select id, contact__c, stagename from opportunity where contact__c != '']; is a bad query
  • Split large methods into few small methods. Method execute is too large
  • Pass as queryLocator not just String of query, but static SOQL query. It could prevent from having runtime exceptions to compile exceptions, that are more easy to fix. instead of Database.getQueryLocator('select id from Account'); use Database.getQueryLocator([select id from Account]);
  • Catch exceptions only if you know what to do with it, otherwise you will not receive any errors if those occur

try{
    delete contactsToDelete;
}
catch(Exception e){} // is not a best practice

your code could be re-write to query Contacts, that are not in Contact__c on Opportunities

global class DeleteContact implements Database.Batchable<sObject>{
    private Date dateToDeleteTo;

    global DeleteContact(){
        dateToDeleteTo = Date.today().addYears(-1).addDays(-3);
    }

    global Database.QueryLocator start(Database.BatchableContext BC){
        Id b2cId = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('B2C Contact').getRecordTypeId();
        return Database.getQueryLocator([
            select Id
            from Contact
            where Id not in (
                select Contact__c
                from Opportunity
                where Contact__c != null
                )
            and RecordTypeId = :b2cId
            and Email like '%@icslearn.co.uk'
            and CreatedDate <= :dateToDeleteTo
            ]);
    }

    global void execute(Database.BatchableContext BC, List<Contact> scope){
        delete scope;
    }
    global void finish(Database.BatchableContext BC){
    }
}
1
  • I would bet that the OP would also prefer the behavior of Database.delete(scope, false) to still delete rows that don't fail the deletion even if one does. If he wanted to add better error logging this would also help facilitate that. Commented Oct 10, 2018 at 19:23
6

You can easily optimize this code. First query Opportunity as child records instead of querying all opportunity in system. Which will also reduce your line of code.

global class DeleteContact implements Database.Batchable<sObject>{

global date d = system.today().addYears(-1).addDays(-3);
//check and verify relationship name
public String query = 'select id, opportunity__c, RecordTypeId, email, customer_Number__c, (SELECT ID FROM Opportunity__r LIMIT 1) from contact where createddate <= :d';

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

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


    Id b2cId = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('B2C Contact').getRecordTypeId();

    List<Contact> contactsToDelete = new List<Contact>();
    // just check the child list size . if its 0 then it don't have record and you can proceed to delete
    for(Contact co : contactsInScope){
            if(co.Opportunity__r.size() == 0 && co.RecordTypeId == b2cId && co.Customer_Number__c == '' && !co.email.contains('@icslearn.co.uk')){
                contactsToDelete.add(co);
            }
    }
     //As this is Asyn so try catch won't make a difference here and you can remove them
    delete contactsToDelete;

    }

    global void finish(Database.BatchableContext BC){

    }
}

You must log in to answer this question.

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