I have the following code that executes exactly how I need it to, the only issue is I receive "Apex CPU time limit exceeded" when I run the delete on Accounts, EmailMessage and update Cases. After looking into the error it looks like I need to move this code into a batch job however all of the documentation around batch jobs only ever delete one SObject. How can I execute this code in a batch job since I am looking at 3 SObjects?

The code basically deletes any accounts, cases and emailmessages that are over 30 days old. If the account has a new case then we ignore the old cases until that new case is 30 days old.

    Date threshold = Date.today().addDays(-30);
    List<Account> accountsToDelete = new List<Account>();
    List<Case> casesToDelete = new List<Case>();
    List<EmailMessage> messagesToDelete = new List<EmailMessage>();
    Integer count = 0;
    Integer count2 = 0;
    Integer count3 = 0;
    for (Account account : [
        SELECT (SELECT LastModifiedDate FROM Cases ORDER BY LastModifiedDate DESC)
        FROM Account WHERE Id IN (
            SELECT AccountId FROM Case
            WHERE LastModifiedDate < :threshold
            AND Account.Name Like '%Test%'

        if (account.Cases[0].LastModifiedDate < threshold)
    messagesToDelete = [ SELECT Id, ParentId FROM EmailMessage WHERE ParentId IN : casesToDelete];

    for(Case c: casesToDelete){
        c.OwnerId = '005i0000001I5Y3';
        c.Guest_Email__c = '';
        c.Guest_Name__c = 'Purged';

    for(Account a: accountsToDelete){
        a.Id = '0018A000006Xvgo';

    for(EmailMessage e: messagesToDelete){
        e.FromAddress = '';
        e.ToAddress = ''

    update casesToDelete;
    delete accountsToDelete;
    delete messagesToDelete;
  • This code is broken in several ways. Before you worry about making this batchable, you should make sure that it's error-free.
    – sfdcfox
    Jun 29, 2016 at 16:22

1 Answer 1


When dealing with parent and child records in most batch processes, you just need to first query for the parent, and then its children:

global class DeleteOldRecords implements Database.Batchable<SObject> {
    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            [SELECT Id FROM Account 
             WHERE LastModifiedDate < LAST_N_DAYS:30 AND 
             Name LIKE '%test%']);
    global void execute(Database.BatchableContext bc, Account[] scope) {
        Set<Id> accountIdsToKeep = new Map<Id, AggregateResult>(
            [SELECT AccountId Id FROM Case WHERE AccountId IN :scope
             AND LastModifiedDate = LAST_N_DAYS:30 GROUP BY AccountId]).keySet();
        Set<Id> accountIdsToDelete = new Map<Id, Account>(scope).keySet();
        if(!accountIdsToDelete.isEmpty()) {
            Case[] casesToDelete = [SELECT Id FROM Case WHERE AccountId IN :accountIdsToDelete];
            EmailMessage[] emailsToDelete = [SELECT Id FROM EmailMessage WHERE ParentId IN :casesToDelete];
            Account[] accountsToDelete = [SELECT Id FROM Account WHERE Id IN :accountIdsToDelete];
            delete emailsToDelete;
            delete casesToDelete;
            delete accountsToDelete;
    global void finish(Database.BatchableContext bc) {

When you run this, you can adjust the batch size to figure out what the optimal number is, since batch processes still have governor limits.

Some general notes:

  • Don't use hard-coded ID values, because they may change or break your tests.
  • Don't change the Id field of a record before deleting it, as you'll delete an entirely different record.
  • Delete doesn't save new field values, so if that's your intent, you must do so before the delete occurs.
  • I'm pretty sure you don't need to delete the emails, since the cascade delete on cases should probably work. You might actually be doing more work than necessary.

You must log in to answer this question.

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