2

My anonymous de-dupe Account apex code runs successfully until it hits a DML limit on the merge execution. I do not want to reduce the size of my query. How do I implement this in batch apex so that it is aware of governor limits?

In particular, I want it to run in batches of appropriate size so that it can complete without giving me "too many DML statements".

// Assumption is accounts exist with dupe DunsNumber

List<Account> allMasters = [SELECT Id, DunsNumber FROM Account WHERE CreatedBy.LastName!='Smith' AND DunsNumber!=null];
List<Account> allPotentialDupes = [SELECT Id, DunsNumber FROM Account WHERE CreatedBy.LastName='Smith' AND DunsNumber!=null];

Map<Account, List<Account>> mastersDupes = new Map<Account, List<Account>>();

for (Account master : allMasters){
    List<Account> mSDupes = new List<Account>();
    for (Account dupe : allPotentialDupes){
        if(master.DunsNumber == dupe.DunsNumber){ //dupe account identified so add it to the mastersDupes list
            mSDupes.add(dupe);
        }
    }

    mastersDupes.put(master, mSDupes);

}

for (Account masterAcct : mastersDupes.keySet()) { //for every key in mastersDupes
    List<Account> keysDupeList = new List<Account>();
    keysDupeList.addAll(mastersDupes.values()[0]);
    for (Account mergeAcct : keysDupeList) //mergeAcct with the masterAcct
    {
        try {
        merge masterAcct mergeAcct;
        } catch (DmlException e) {
        // Process exception
        System.debug('An unexpected error has occurred: ' + e.getMessage()); 
        }
    }

}
1
  • You can't implement Batchable, Queueable, or Schedulable in an execute anonymous script. You'll either get an Internal Server Error or a "only top level classes can implement X interface" errors. You'll need to write a proper class, deploy it to production, and then run it.
    – sfdcfox
    Commented Feb 14, 2017 at 18:43

2 Answers 2

3

If you're just using an anonymous script and trying to knock this task out quickly, you can add a limits check and just bail once you've performed the maximum number of DML Statements.

for (...)
{
    if (Limits.getLimitDmlStatements() > Limits.getDmlStatements())
    {
        merge ...
    }
    else
    {
        break;
    }
}
2
  • Just curious, can we use getLimitDMLRows() for this?
    – javanoob
    Commented Feb 14, 2017 at 21:05
  • @javanoob It's extremely unlikely that will be the limitation with merge statements. You can only act on up to three records per statement.
    – Adrian Larson
    Commented Feb 14, 2017 at 21:14
4

You can reduce your DML statements by up to half by using the alternative format for merge, which takes a single record and two related records:

Account master = dups.remove(0);
Account[] children = new Account[0];
while(!dups.isEmpty()) {
    while(!dups.isEmpty() && children.size() < 2) {
        children.add(dups.remove(0));
    }
    try {
        merge master children;
    } catch(DmlException e) {
        // Report an error
    }
    children.clear();
}

As for the first loop-within-a-loop, you could use a map for efficiency:

Map<String, Account[]> dupList = new Map<String, Account[]>();
for(Account record: allMasters) {
    if(!dupList.containsKey(record.DunsNumber)) {
        dupList.put(record.DunsNumber, new Account[0]);
    }
    dupList.get(record.DunsNumber).add(record);
}

As far as making it batchable? I wouldn't. Instead, use Queueable to continue as long as you have more work to do:

List<Account[]> accounts = dupList.values();
System.enqueueJob(this);

...

public void execute(Database.QueueableContext context) {
    doMergeLogic();
    if(!accounts.isEmpty()) {
        System.enqueueJob(this);
    }
}

Queueable is a lot more appropriate than batchable in this case, because you don't know how many DML statements you need, and batchable's size can't be dynamically changed midway.

4
  • Sounds like a one-off job here where the priority is to get it done as quickly as possible. Nice efficiency tips though. And an interesting note about Queueable vs. Batchable.
    – Adrian Larson
    Commented Feb 14, 2017 at 18:39
  • @AdrianLarson Yeah, the database and system interfaces you'd use to script queueable, schedulable, or batchable can't be implemented without actually having a compiled class saved in the database. You'll just either get compilation errors, or if you really force the issue, an Internal Server Error.
    – sfdcfox
    Commented Feb 14, 2017 at 18:44
  • Right, that's what I'm saying, they'd have to go through the whole development/deployment cycle, so involving a proper class which implements any of those interfaces may be more effort than it's worth in the short term
    – Adrian Larson
    Commented Feb 14, 2017 at 18:45
  • 2
    @sfdcfox and Adrian - next time you two are in Palo Alto, happy hour is on me! Both of your answers are incredibly helpful. Thank you sfdcfox for the education here, i did not know those things. Adrian that's exactly what I need, but I asked the wrong question and sfdcfox helped illustrate that. Choosing his answer so that it rises to the top to help others who face similar confusion. Commented Feb 14, 2017 at 19:09

You must log in to answer this question.

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