Because the original Salesforce Cookbooks are no longer available online, I’m putting this code here so other people can benefit from it. I’m pulling (and cleaning up a bit) from the 2010 Cookbook.
Discover an effective solution for preventing duplicate leads in Salesforce with this comprehensive Apex code cookbook. Because the original Salesforce Cookbooks are no longer available online, this resource offers valuable code snippets to address the challenge of ensuring uniqueness based on the value of a standard field. The provided Apex trigger, designed for both before insert and before update events, efficiently utilizes a map to identify and handle duplicate leads. The included class for testing ensures the trigger’s robustness in handling single and bulk-record inserts and updates.
(BEGIN QUOTE)
If you need to require uniqueness based on the value of two or more fields, or a single standard field, write an Apex before insert and before update trigger. For example, the following trigger prevents leads from being saved if they have a matching Email field:
- The trigger first uses a map to store the updated leads with each lead’s email address as the key.
- The trigger then uses the set of keys in the map to query the database for any existing lead records with the same email addresses. For every matching lead, the duplicate record is marked with an error condition.
trigger leadDuplicatePreventer on Lead(before insert, before update) {
Map<String, Lead> leadMap = new Map<String, Lead>();
for (Lead lead : Trigger.new) {
// Make sure we don't treat an email address that
// isn't changing during an update as a duplicate.
if ((lead.Email != null) && (Trigger.isInsert || (lead.Email != Trigger.oldMap.get(lead.Id).Email))) {
// Make sure another new lead isn't also a duplicate
if (leadMap.containsKey(lead.Email)) {
lead.Email.addError('Another new lead has the ' + 'same email address.');
} else {
leadMap.put(lead.Email, lead);
}
}
}
// Using a single database query, find all the leads in
// the database that have the same email address as any
// of the leads being inserted or updated.
for (Lead lead : [SELECT Email FROM Lead WHERE Email IN :leadMap.KeySet()]) {
Lead newLead = leadMap.get(lead.Email);
newLead.Email.addError('A lead with this email ' + 'address already exists.');
}
}
The following class can be used to test the trigger for both single- and bulk-record inserts and updates.
@isTest
public class LeadDupePreventerTests {
@isTest
private static void testLeadDupPreventer() {
// First make sure there are no leads already in the system
// that have the email addresses used for testing
Set<String> testEmailAddress = new Set<String>();
testEmailAddress.add('test1@duptest.com');
testEmailAddress.add('test2@duptest.com');
testEmailAddress.add('test3@duptest.com');
testEmailAddress.add('test4@duptest.com');
testEmailAddress.add('test5@duptest.com');
Assert.areEqual(0,
[SELECT COUNT()
FROM Lead
WHERE Email IN :testEmailAddress]);
// Seed the database with some leads, and make sure they can
// be bulk inserted successfully.
Lead lead1 = new Lead(LastName = 'Test1', Company = 'Test1 Inc.', Email = 'test1@duptest.com');
Lead lead2 = new Lead(LastName = 'Test2', Company = 'Test2 Inc.', Email = 'test4@duptest.com');
Lead lead3 = new Lead(LastName = 'Test3', Company = 'Test3 Inc.', Email = 'test5@duptest.com');
Lead[] leads = new List<Lead>{ lead1, lead2, lead3 };
insert leads;
// Now make sure that some of these leads can be changed and
// then bulk updated successfully. Note that lead1 is not
// being changed, but is still being passed to the update
// call. This should be OK.
lead2.Email = 'test2@duptest.com';
lead3.Email = 'test3@duptest.com';
update leads;
// Make sure that single row lead duplication prevention works
// on insert.
Lead dup1 = new Lead(LastName = 'Test1Dup', Company = 'Test1Dup Inc.', Email = 'test1@duptest.com');
try {
insert dup1;
Assert.isTrue(false);
} catch (DmlException e) {
Assert.isTrue(e.getNumDml() == 1);
Assert.isTrue(e.getDmlIndex(0) == 0);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('A lead with this email address already exists.') > -1);
}
// Make sure that single row lead duplication prevention works
// on update.
dup1 = new Lead(Id = lead1.Id, LastName = 'Test1Dup', Company = 'Test1Dup Inc.', Email = 'test2@duptest.com');
try {
update dup1;
Assert.isTrue(false);
} catch (DmlException e) {
Assert.isTrue(e.getNumDml() == 1);
Assert.isTrue(e.getDmlIndex(0) == 0);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('A lead with this email address already exists.') > -1);
}
// Make sure that bulk lead duplication prevention works on
// insert. Note that the first item being inserted is fine,
// but the second and third items are duplicates. Note also
// that since at least one record insert fails, the entire
// transaction will be rolled back.
dup1 = new Lead(LastName = 'Test1Dup', Company = 'Test1Dup Inc.', Email = 'test4@duptest.com');
Lead dup2 = new Lead(LastName = 'Test2Dup', Company = 'Test2Dup Inc.', Email = 'test2@duptest.com');
Lead dup3 = new Lead(LastName = 'Test3Dup', Company = 'Test3Dup Inc.', Email = 'test3@duptest.com');
Lead[] dups = new List<Lead>{ dup1, dup2, dup3 };
try {
insert dups;
Assert.isTrue(false);
} catch (DmlException e) {
Assert.isTrue(e.getNumDml() == 2);
Assert.isTrue(e.getDmlIndex(0) == 1);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('A lead with this email address already exists.') > -1);
Assert.isTrue(e.getDmlIndex(1) == 2);
Assert.isTrue(e.getDmlFields(1).size() == 1);
Assert.isTrue(e.getDmlFields(1)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(1).indexOf('A lead with this email address already exists.') > -1);
}
// Make sure that bulk lead duplication prevention works on
// update. Note that the first item being updated is fine,
// because the email address is new, and the second item is
// also fine, but in this case it's because the email
// address doesn't change. The third case is flagged as an
// error because it is a duplicate of the email address of the
// first lead's value in the database, even though that value
// is changing in this same update call. It would be an
// interesting exercise to rewrite the trigger to allow this
// case. Note also that since at least one record update
// fails, the entire transaction will be rolled back.
dup1 = new Lead(Id = lead1.Id, Email = 'test4@duptest.com');
dup2 = new Lead(Id = lead2.Id, Email = 'test2@duptest.com');
dup3 = new Lead(Id = lead3.Id, Email = 'test1@duptest.com');
dups = new List<Lead>{ dup1, dup2, dup3 };
try {
update dups;
Assert.isTrue(false);
} catch (DmlException e) {
System.debug(e.getNumDml());
System.debug(e.getDmlMessage(0));
Assert.isTrue(e.getNumDml() == 1);
Assert.isTrue(e.getDmlIndex(0) == 2);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('A lead with this email address already exists.') > -1);
}
// Make sure that duplicates in the submission are caught when
// inserting leads. Note that this test also catches an
// attempt to insert a lead where there is an existing
// duplicate.
dup1 = new Lead(LastName = 'Test1Dup', Company = 'Test1Dup Inc.', Email = 'test4@duptest.com');
dup2 = new Lead(LastName = 'Test2Dup', Company = 'Test2Dup Inc.', Email = 'test4@duptest.com');
dup3 = new Lead(LastName = 'Test3Dup', Company = 'Test3Dup Inc.', Email = 'test3@duptest.com');
dups = new List<Lead>{ dup1, dup2, dup3 };
try {
insert dups;
Assert.isTrue(false);
} catch (DmlException e) {
Assert.isTrue(e.getNumDml() == 2);
Assert.isTrue(e.getDmlIndex(0) == 1);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('Another new lead has the same email address.') > -1);
Assert.isTrue(e.getDmlIndex(1) == 2);
Assert.isTrue(e.getDmlFields(1).size() == 1);
Assert.isTrue(e.getDmlFields(1)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(1).indexOf('A lead with this email address already exists.') > -1);
}
// Make sure that duplicates in the submission are caught when
// updating leads. Note that this test also catches an attempt
// to update a lead where there is an existing duplicate.
dup1 = new Lead(Id = lead1.Id, Email = 'test4@duptest.com');
dup2 = new Lead(Id = lead2.Id, Email = 'test4@duptest.com');
dup3 = new Lead(Id = lead3.Id, Email = 'test2@duptest.com');
dups = new List<Lead>{ dup1, dup2, dup3 };
try {
update dups;
Assert.isTrue(false);
} catch (DmlException e) {
Assert.isTrue(e.getNumDml() == 2);
Assert.isTrue(e.getDmlIndex(0) == 1);
Assert.isTrue(e.getDmlFields(0).size() == 1);
Assert.isTrue(e.getDmlFields(0)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(0).indexOf('Another new lead has the same email address.') > -1);
Assert.isTrue(e.getDmlIndex(1) == 2);
Assert.isTrue(e.getDmlFields(1).size() == 1);
Assert.isTrue(e.getDmlFields(1)[0] == 'Email');
Assert.isTrue(e.getDmlMessage(1).indexOf('A lead with this email address already exists.') > -1);
}
}
}
Discussion
The first and most important lesson to learn from this recipe is that you should generally take advantage of point-and-click Force.com functionality if it can solve your problem, rather than writing code. By using the point-and-click tools that are provided, you leverage the power of the platform. Why reinvent the wheel if you can take advantage of a point-and-click feature that performs the same functionality? As a result, we indicate in this recipe that you should first determine whether you can simply use the Unique and Required checkboxes on a single custom field definition to prevent duplicates.
If you do need to check for duplicates based on the value of a single standard field, or more than one field, Apex is the best way to accomplish this. Because Apex runs on the Force.com servers, it’s far more efficient than a deduplication algorithm that runs in a Web control. Additionally, Apex can execute every time a record is inserted or updated in the database, regardless of whether the database operation occurs as a result of a user clicking Save in the user interface, or as a result of a bulk upsert call to the API. Web controls can only be triggered when a record is saved through the user interface.
The included trigger is production-ready because it meets the following criteria:
- The trigger only makes a single database query, regardless of the number of leads being inserted or updated.
- The trigger catches duplicates that are in the list of leads being inserted or updated.
- The trigger handles updates properly. That is, leads that are being updated with email addresses that haven’t changed are not flagged as duplicates.
- The trigger has full unit test coverage, including tests for both single- and bulk-record inserts and updates.
(END QUOTE)
Note that this only handles Leads. The Cookbook doesn’t check against Contacts.
Also, I know the trigger has logic – this is taken from the Cookbook! Update it to work with your trigger handler framework (you DO use one, right?).
Whether you’re a Salesforce developer seeking a reliable duplicate prevention strategy or an administrator looking to enhance data quality, this resource provides a practical and production-ready solution. Take advantage of this Salesforce Apex code cookbook to efficiently manage duplicate leads and elevate the performance of your Salesforce instance.
Share Your Thoughts