Preface – This post is part of the Write Your First Intermediate Trigger series.
Ok guys let’s write a trigger that will prevent a user from creating a lead that already exists as a contact! We’ll use the lead/contact’s email address to detect duplicates.
A brilliant person once told me that even the most complicated code can be broken down into tiny, individual steps that are easy to implement. Let’s see what our steps are:
Now, try to match the steps above with the code! Each step should match to at least one line of code.
trigger FindDupes on Lead (before insert, before update) { for (Lead myLead : Trigger.new) {if (myLead.Email != null) {List<Contact> dupes = [SELECT Id FROM Contact WHERE Email = :myLead.Email];if (dupes.size() > 0) {} }myLead.Dupe_Contact__c = dupes[0].Id;} else {myLead.Dupe_Contact__c = null;} }
Note that users will receive an error message via a validation rule anytime the Dupe_Contact__c field is populated. Apex is often mixed with point-and-click functionality!
And in case you’ve forgotten…
Next post: An extended look at our deduping trigger!
@David, this trigger doesn’t work when trying to insert leads using the data loader or API. Can you please help on how to do it?
This is one of the interview questions asked how to resolve it as it fails with data loader and works for a single record.
trigger nelead on Lead (before insert,before update) {
//system.debug(‘the value is’ +add);
list emailids=[select email from Contact where email !=null];
for(Lead Le:trigger.new){
if(Le.email==null){
le.addError(’email address should not be empty’);
}
for (contact con:emailids ){
//if(con.size()>0){
if(le.email ==con.email)
{
le.addError(’email should not be same as contact’);
}
//}
}
}
}
I tried above code .it is working as excepted.Could you check and let me know whether it is correct or not.
Hey David,
While the code works for single lead creation, this will be a problem for bulk Lead inserts (SOQL more than 100). I was playing around for bulk check and this works!
Trigger Code:
trigger LeadTrigger on Lead (before insert, before update) {
LeadHandler handler = new LeadHandler();
if (Trigger.isBefore) {
if (Trigger.isInsert) {
handler.onBeforeInsert(Trigger.new);
}
if (Trigger.IsUpdate) {
// handler.onBeforeUpdate(Trigger.new, Trigger.Old);
}
// More event checks if needed
}
}
Handler code:
public class LeadHandler {
Public void onBeforeInsert(List newLeadList) {
Map emailLeadMap = new Map();
for (Lead leadVar : newLeadList) {
emailLeadMap.put(leadVar, leadVar.email);
}
checkDupes(emailLeadMap);
}
private void checkDupes(Map emailLeadMap) {
Map emailContactMap = new Map();
// Get all contacts with matching emails
for (Contact contactVar : [SELECT Email FROM Contact WHERE Email IN :emailLeadMap.values()]) {
// This map can be replaced by Set of emails as well :)
emailContactMap.put(contactVar.email, contactVar);
}
// For every incoming Lead, check if contacts with matching emails were found
for (Lead leadVar : emailLeadMap.keyset()) {
if (emailContactMap.containskey(leadVar.email)) {
leadVar.addError(‘ContactAlreadyExist’);
}
}
}
}
Tested it with Data Import Wizard for about 200 test leads and it allows partial inserts. Pardon my missing comments :)
^_^ Hi Sid!
Hi David, usually I answer those questions too, but today i have a question about de-Dupe.
Is there any way we can accomplish the Bulk depupe on behalf of the email using SOSL instead of SOQL,
Issue i facing is that I can’t use select id, email from contact where email =:’xyz’
I need to use FIND :’xyz’ in Email fields returning Contact(id,email)
for a singly user input it is not an issue but on bulk (more than one record) …
Do you have any idea
Both work!
SOSL for multi object, and SOQL for single / few objects =)
But how?
The trigger is like a list or a map
Let’s imagine we have the list new contacts
And we have already filtered out into a map the emails
Contact I’d , email
How do I iterate now through the map values aka the email
Using the Find without having it inside a loop
I can’t use the where clause on email inside the find
The find is restricted to 20 call per webcall
The value I compare is the search string of the find
Find :searchstr in email fields returning contact(Id,email)
Where clause is not possible as the email field is encrypted
I can’t translate the list of email into a joined or string because the searchstr is restricted to 4000 character with containing operator
Cheers
Excellent code!
Hi David,
I’m not getting what type of field dupes is?
Hello David,
I am new to Salesforce. I designed this trigger .[No Custom Object & No Validation ]
trigger FindDupe on Lead (before insert,before update) {
for(Lead myLead:Trigger.new){
if(myLead.Email !=null){
List dupes=[SELECT Id FROM Contact where Email=:myLead.Email];
if(dupes.Size() >0)
myLead.addError(‘Contact with this Email already exits with ID :’+dupes[0].Id);
}
}
}
trigger DeDupeLead on Lead (before insert,before update)
{
//Declares a List variable to hold all Contact Id’s, Emails, First and Last Names where the Email is present
List c = [SELECT Id, Email, FirstName, LastName FROM Contact WHERE EMAIL != Null];
//Declares an Integer variable n to hold the size of list c
Integer n = c.size();
//Declares an integer variable for a later For loop
Integer i = 0;
//Creates a loop that passes every lead initiation the trigger to Trigger.new
for (Lead l : Trigger.new)
{
//Loop iterates through every element in the list c
for(i = 0; i < n; i++)
{
//Condition evaluates whether the email of each item in the list c matches that of the lead being created or updated
if(l.Email == c[i].Email)
{
//Fires a validation message if a lead is being created or updated with a duplicate email
l.addError('A duplicate Contact exists'+ c[i].FirstName + ' ' + c[i].LastName);
}
}
}
}
This trigger does not complies to best practice. You should not query the entire list of contacts with a filter Email != Null. There is a more chance of hitting the limits.
Many Thanks David. Created first trigger. Simply thanks Man.
I am a novice to coding and you have inspired me.
Thanks
Charu
Good job Charu!!
trigger Duplicate_email on Contact (before insert) {
list listcon = new list();
List conemail = [select email from contact];
if(trigger.isInsert){
for(Contact con: trigger.new){
for(contact co: conemail){
if(con.Email== co.email && co.email!=null){
con.adderror(‘Please Give valid Email’);
}
}
}
}
}
David,
Are you able to display a custom error message where the user can actually click on the contact id of the duplicate record or at the very least view the contact id?
Never mind David, after searching below I was able to code my own answer! :)
trigger DupeCheck on Lead (before insert, before update) {
for(Lead myLead : Trigger.new){
if(myLead.Email != null){
List dupes = [SELECT Id FROM Contact WHERE Email = :myLead.Email];
if(dupes.size() > 0){
myLead.Duplicate_Record_Found__c = dupes[0].Id;
//Display Error Message
myLead.addError(‘There is already a contact with this email address! ‘+ myLead.Duplicate_Record_Found__c);
}
else{
myLead.Duplicate_Record_Found__c = null;
}
}
}
}
Well done!
Thanks David! One additional question though. The code breaks when I try to convert the lead to a contact. (It shows my custom error message) Why is this?
Are you sure there isn’t a dupe? If not, you might be detecting yourself!
Positive there is no dupe. I tried this with multiple leads. What do you mean by detecting myself?
The Contact created during conversion might be the “dupe”!
Yes, that is definitely what is happening. How do I avoid this from triggering the trigger?
Oooooo this one is such a good one I can’t tell you or even point you in the right direction. You might not like this answer but it’s so juicy that when you figure it out you can thank me later!!
I fixed a small error, but still having the same issue. Here is the updated code. Can I at least get a hint? You’re killing me, lol. By the way, thanks for sticking with me this far. I really appreciate it :)
trigger FindDupes on Lead (before insert, before update) {
for(Lead myLead : Trigger.new){
//create a list of contacts that have an email equal to myLead
List dupes = [SELECT Id FROM Contact WHERE Email = :myLead.Email];
//check if anything is in the dupes list
if(dupes.size() > 0){
myLead.Dupe_Contact__c = dupes[0].Id;
myLead.addError(‘There is already a contact with this email!’);
}
else{
myLead.Dupe_Contact__c = null;
}
}
}
After “List” in the code I have the word “Contact” inside brackets but when I post the comment it deletes the word for some reason.
OMG I think this fixes it!
if(mylead.ConvertedContactId == null)
Is this what you were thinking?
That’ll do it!!!!!
Worth working hard for right!?! =)
Good job Chris!
David
You were right David it was worth it. You’re the man! BTW I attended my first user group meeting last night in Philly and met Steve Molis (he is also the man, lol) Thanks again for your guidance!
-Chris
Cheers Chris, was just thinking about this while scrolling through comments!
Just in case anyone has any errors, I had a couple:
trigger FindDupes2 on Lead (before insert, before update) {
for(Lead myLead : Trigger.new){
if(myLead.Email != null){
List dupes = [SELECT Id FROM Contact WHERE Email = :myLead.Email];
if(dupes.size() > 0){
myLead.Dupe_Contact__c = dupes[0].Id;
//Display Error Message
myLead.addError(‘There is already a contact with this email address!’ + myLead.Dupe_Contact__c);
}
else{
myLead.Dupe_Contact__c = null;
}
}
}
}
how did you get Duplicate_Record_Found__c. What is it ?
HI David,
In your code you are queering inside the for loop. I hope this trigger wont work in bulk insert.
Thanks,
Arjun.
Good call – I cover that in Chapter 5!
In this chapter trigger ,we are checking for the duplicates in inserted records but what if there are duplicates in the batch of records being inserted.
thanks in advance
Hi David
For the below
myLead.Dupe_Contact__c = dupes[0].Id;
Did you add the field Dupe_Contact__c to the Lead object ? It is not there in the sandbox by default ?
Yup – it’s a custom field!
David,
It should be Dupe_Contact__c instead of Contact_Dupe__c in the line just below your code snippet:
“Note that users will receive an error message via a validation rule anytime the Contact_Dupe__c field is populated. Apex is often mixed with point-and-click functionality!”
Fixed, thank you Mayank!
its gives error
Error Error: Compile Error: Invalid field Dupe_Contact__c for SObject Lead at line 7 column 9
how i solve this error
Gotta create the custom field!
Hi David sorry, i am new at this, what kind?
Hi David,
I appreciate your work.
I wanted to know that I have created trigger on events to check if startdatetime field is not either at hours or at half hours round it up.
EXAMPLE : if i select 10.15 am it should round up to 10.30 am and if i select 10.50 So it will round up on 11.00 AM.
But I am not getting as mentioned. My code is below.
trigger EventOnTime on Event (Before Insert, Before Update) {
//SomeClass.setTime(trigger.new);
for(Event so : trigger.new){
if(so.StartDateTime == null ){
continue;
}
if(so.StartDateTime.minute() > 30 ){
so.StartDateTime = so.StartDateTime.addMinutes(60 – so.StartDateTime.minute());
}
else{
so.StartDateTime = so.StartDateTime.addMinutes(30 – so.StartDateTime.minute());
}
}
}
Thanks in Advance
What’s the error message you’re getting?
Try posting this on the forums you might get a quicker response!
https://www.sfdc99.com/forums/forum/beginning-apex/
Hi Sarvesh, I didn’t test it but it looks like that you mess up the startDateTime here
I might be wrong but I would try
for(Event so : trigger.new){
if(so.StartDateTime != null ){
if(so.StartDateTime.minute() > 30 ){
so.StartDateTime.addMinutes(60 – so.StartDateTime.minute());
}
else{
so.StartDateTime.addMinutes(30 – so.StartDateTime.minute());
}
}
}
}
As you are already within the trigger with the current values ( before insert / update)
the startDateTime is lets say already 11:15
if you now simply add minutes that should do the trick
Before you have tried to assign the value of just the minutes to the startDatetime
so you need to either say
so.StartDateTime = so.StartDateTime + so.StartDateTime.addMinutes(30 – so.StartDateTime.minute());
or as I suggest only add the minutes
Also I changed the IF criteria from == towards != null
Let me know if that helps
Hello David,
Thank you for putting the sfdc99 resource together for guys(/and girls) like me. I am new to coding, thanks to your site (and constant google searches) I feel like I am starting to understand what is apex coding. (Cant say yet that I understand how to code apex, but man it is night and day from before I found this site)
I am currently an Admin and have been trying to find a way to prevent duplicate records with in our salesforce contacts object. I thought I would take a stab at writing code for this and modeled it upon your example found in your beginner lessons.
Here is my logic.
1. Contact record is created or updated
2. Contact record has a Name and Birthdate (and are not empty fields)
3. Try to find a matching Contact based upon Name and Birthdate Fields
4. If a match is found, give the user an error message
5. If a match is not found, do nothing
This is my code:
trigger FindDupes on Contact (before insert, before update) {
for (Contact myContact : Trigger.new) {
if (myContact.Name != null) {
if (myContact.Birthdate != null) {
List dupes = [SELECT Id FROM Contact
WHERE Name = :myContact.Name
AND Birthdate = :myContact.Birthdate];
if (dupes.size() > 0) {
String errorMessage = ‘Duplicate contact found! ‘;
errorMessage += ‘Record ID is ‘ + dupes[0].Id;
myContact.addError(errorMessage);
}
}
}
}
}
I have created a sandbox and added this trigger to the Contacts trigger section. Salesforce accepted the trigger with no error message (it took me many multiple attempts to get no errors), however, when I go to test this code and create several duplicate records – I don’t see an error message when I should, under instances that i create a duplicate contact (name and Birthdate) or anytime I update a record (that is a duplicate record).
Am I missing something or just completely off base? (idk anymore I’ve been staring at my computer screen for hours all weekend trying to figure it out). Any help is greatly appreciated.
Thanks in Advance
-Nate
Great job on the trigger Nate!
…it actually looks perfect to me!
My advice is to keep reducing the trigger until it works! (Also, make sure it’s marked as Active).
For example, try any of these:
– Just check for an exact Name match (perhaps Last Name instead)
– Always add an error to the record
– Use System.debug
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
Hi Nate / David
I had a quick test and inserted
system.debug(‘ Name= ‘ + myContact.Name + ‘ bday= ‘ + myContact.Birthdate);
before and within the IF clause
and from the debug it looks like that the NAME fields concatenation from Salutation First / Middle / Last name and suffix only
takes place once the record is saved.
The debug shows the NAME with a null value,
therefore you will need to take each single field of the name into account
so amend your trigger from where Name =: myContact.name
into where Firstname =: myContact.Firstname AND LastName =: myContact.LastName
across the whole trigger you might like to add salutation as well
Hope that helps
David (and Andreas),
Thank you for your reply.
Let me first say that sfdc99 is awesome. I only found your website here David this past weekend, however, I have become possessed in apex and within a week deployed into my production org my first trigger. My path is just beginning but I already have learned quite a bit so I am grateful for your time and energy creating this resource for the 99%.
Okay here is what I did;
Andreas thanks for your advice with Name vs FirstName LastName, it definitely helped. However I still ran into a problem with getting the + 75% coverage needed to deploy. I would at best only get 55%, just changing that aspect of the code. I did some additional research (and David tell me if this comports with your knowledge) as I came up with this after I realized that perhaps binding with a date doesn’t work very well. So the work around was to create a custom formula field called [ of_days_old__c ]. Essentially I created a formula [ TODAY() – Birthdate) ] to turn this date into an integer which would yield a simple ‘primitive’ number. I then bound this expression together [of_days_old__c = :myContact.of_days_old__c ]. After running my test class with only inserting FirstName LastName and Birthdate (not my new custom field to test and see if it works, see test class below) to my surprise I got 100% coverage.
The following is how my trigger looks now ( all feedback is appreciated)
trigger FindDupess on Contact (before insert, before update) {
for (Contact myContact : Trigger.new) {
if (myContact.LastName != null && myContact.FirstName != null && myContact.Birthdate != null) {
List dupes = [SELECT Id FROM Contact
WHERE FirstName = :myContact.FirstName
AND LastName = :myContact.LastName
AND of_days_old__c = :myContact.of_days_old__c ] ;
if (dupes.size() > 0) {
String errorMessage = ‘Duplicate contact found! ‘;
errorMessage += ‘Record ID is ‘ + dupes[0].Id;
myContact.addError(errorMessage);
}
}
}
}
the following is how my test class was composed
@isTest
public class TestFindDupes {
static testMethod void testDupes() {
// Let’s create our records from scratch!
Contact c = new Contact();
c.FirstName = ‘Stephen’;
c.LastName = ‘Curry’;
c.Birthdate = Date.newInstance(2006, 3, 17);
insert c;
// Now let’s create a dupe contact
Contact dupeContact = new Contact();
dupeContact.FirstName = ‘Stephen’;
dupeContact.LastName = ‘Curry’;
dupeContact.Birthdate = Date.newInstance(2006, 3, 17);
try {
insert dupeContact;
} catch (Exception e) {
System.debug(‘An error happened, as predicted!’);
}
// Now we try to find our dupe contact, by name and dob
List dupes = [SELECT Id FROM Contact
WHERE Name = ‘steph curry’
AND Birthdate = :Date.newInstance(2006, 3, 17)];
System.assertEquals(0, dupes.size());
// Now we “break” our trigger by inserting a non-dupe
Contact legitContact = new Contact();
legitContact.FirstName = ‘David’;
legitContact.LastName = ‘Lee’;
legitContact.Birthdate = Date.newInstance(2007, 3, 17);
insert legitContact;
// Now we try to find our legit contact, by name and dob
List legits = [SELECT Id FROM Contact
WHERE Name = ‘david lee’
AND Birthdate = :Date.newInstance(2007, 3, 17)];
System.assertEquals(1, legits.size());
}
}
David my hats off to you, I modeled both of the above on what you have provided us here on sfdc99. I could not have accomplished this in 5 days ANYWHERE else. Thanks A-Million ( I owe you a case of mikes hard lemonade if/when we ever meet up).
– Nate
Hi David,Andreas and NH,
I tried to implement the same trigger . But, I am getting the error.
trigger dupcon on Contact (before insert, before update){
for (Contact c :trigger.new) {
//c.adderror(‘Hello’);
if (c.FirstName != null && c.LastName!= null && c.BirthDate!= null) {
c.adderror(‘Hello’);
List dupcon = [SELECT Id FROM Contact WHERE FirstName = :c.FirstName AND LastName = :c.LastName ];
if (dupcon.size() >0 ) {
String Errmsg = ‘ERROR’;
Errmsg += ‘Record Id’+dupcon[0].Id;
c.adderror(Errmsg);
}
}
}
}
Seems like the If condition doesn’t work. When i tried to save a duplicate Contact, its getting Saved. I tried to add an error message just before the if statement. Then “hello” appears along with an Error msg saying “Contacts not associated with accounts are private and cannot be viewed by other users or included in reports.” . Thanks in Advance :)
Today the above error got disappeared.. :) wow..
Great job Reshmi!!
Hi Nate
myContact.addError(errorMessage);
Where is the method addError impmented ?
Hi David,
I just had a quick question about this line of code from the lead dedupe trigger:
“errorMessage += ‘Record ID is ‘ + dupes[0].Id;”
What does “+=” mean in this line of code?
Great question!
It’s the same as doing this:
errorMessage = errorMessage + ‘Record ID is ‘ + dupes[0].Id;
Basically a shortcut for appending stuff
+= is the shortway to express the same as error = error + recordID
so you can simply write error += recordID
Hi Andreas,
here += is work – first it add the value and then asign it.
Like suppose you want to print value 1 to 10 then.
int a = 0;
for ( int i =0; i <=10; i++)
{
a = a + i;
// print a here
}
And you can also do this like
a += i;
Means first add (+) the value and then ( = ) asign it in to a;
As like this.
In the above code
First apend the record id and dupps[0].id then asign this value in to errorMessage.
I hope it'll help you.
Regards
Virendra
Simple consept which you write first that apply first.
Like if you do
” =+ ” then first value is asgin and then add.
You also can do ” -= “.
Right David?
You are 100% right Viru =) Thanks for the clear explanation!
Hi David,
I wrote this code to update a Email Field Called Data partner Contacts email poc, based on the lookup field MCA data partner poc (look up to a custom object MCA data partner contacts, which has name and email). I want the email address on the child object. the code I wrote is not working, and I am unable to figure out why, can you please help? really appreciate it
trigger UpdateDataPartnerContactEmails on MCA_Campaign__c (After Insert, After update) {
//String x = MCA_Campaign__c.MCA_Data_Partner_Contact__r.Contact_Email__c;
for (MCA_Campaign__c Mca : Trigger.new) {
if (Mca.MCA_Data_Partner_Contact__c != null) {
List Partners = [SELECT Id, Contact_Email__c FROM MCA_Data_Partner_Contacts__c WHERE Name = :Mca.MCA_Data_Partner_Contact__c];
if (Partners.size() > 0){
Mca.Data_Partner_PoC_1_email__c = Partners[0].Contact_Email__c;
update Mca;
}
}
}
}
Use this post to help you figure it out!
https://www.sfdc99.com/2014/02/22/debug-your-code-with-system-debug/
David,
Need help to understand this :
Just to check out a requirement i thought of i.e to have a Roll up of Count of Contacts displayed on Account Object.
so i went ahead to create a rollup summary field on Account…but found that in summarized object list contact is not there.
. I have read roll up summary is possible only for master detail relationship, and only for such relationships SF displays an option to create roll up summary. But in here, though it is showing option to create rollup, but not displaying Contact to use.
why so?
Contact to Account is a special type of relationship that doesn’t allow roll-ups =) It’s basically a lookup
Hi David,
I have usecase, wherein in an opportunity I should be able to add only one product. Could be please tell me how to go about it
Thanks
Created a validation rule that makes sure the number of line items is less than 2! No code needed!
Can’t this be done in a declarative way : Setup-> Opportunity Setting-> click on checkboxes 1) Prompt users to add product for an opportunity
2) Once user add the product to an opportunity, user must enter 1
in the quantity.
Hi David,
Not sure if my last comment was received by you, however I continue to go through your blog. Thank you for all your work and my deep respect.
I was trying to implement Lead dedupe the way below using Maps, pls let me know if I am on a right track. Also I am encountering ‘External ID error’, Please advise me on this.
awaiting for your response,
trigger leadDedup on Lead (before insert, before update) {
List conEmailMapLst= [SELECT Id, Name, email from contact];
Map conEmailMap = new map();
for (Contact conList: conEmailMapLst){
conEmailMap.put(conList.Id, conList.email);
}
system.debug(‘Contact Map details—>’+ conEmailMap);
for (Lead lEmail: Trigger.New){
//Going through the Contact email map and verifying if Email exists
if(conEmailMap.containsKey(lEmail.email)){
string errMsg = ‘Email already exists’;
lEmail.addError(errMsg);
}
}
}
Hello Kruthi
i just want to help ! David plz correct me if i am wrong. [ Thanks (-; ]
okay, So first thing that i observe in your code is that you are using List without defining which type of List.
1. ) I mean use List instead of List. [on Line number 2]
And second one is you are using MAP. And same problem with MAP
2.) Write map conEmailMap = new map(); [On line Number 3]
Now Third one is you are trying to check this email is exist or not so you use ‘ ContainsKey ‘ method of MAP, But you know that this method is checking only on key of MAP and return True if it Contains.
3.) You are putting Contact.id on Key side and contact.Email on Value side.so when you using ContainsKey method it check key side and return false because Contact id and email never match.
So my point is put Contact.Email first and then contact.id like conEmailMap.put( conList.email, conList.Id);
And then use ContainsKey method.
It’ll Work.
Thanks
Viru (-;
trigger leadDedup on Lead (before insert, before update) {
Map conEmailMap = new map();
List conEmailMapLst= new List();
conEmailMapLst= [SELECT Id, Name, email from contact];
for (Contact conList: conEmailMapLst){
conEmailMap.put(conList.Id, conList.email);
}
system.debug(‘Contact Map details—>’+ conEmailMap);
for (Lead lEmail: Trigger.New){
if(conEmailMap.containsKey(lEmail.email)){
string errMsg = ‘Email already exists’;
lEmail.addError(errMsg);
}
}
}
So sorry Viru, I had pasted the wrong code :(
Dont mind pls.
However i will surely work on your 3rd point.
Thanks for your inputs.
I guess there is some problem while posting the commenting even now it shows the same, however my code is
mapconEmailMap = new map(); and
List conEmailmapLst = new List();
The code defining type of Map and List is getting eliminated while comment is posted. Anyways, I am creating a Map of
Key-> Id, Value-> string ie,
Key-> Id, Value-> email
which i will modify as you suggested
Key-> email, Value-> Id
Thank you
yeah right there is some problem !
Thank you Viru and great job so far on the code Kruthi!
Viru is 100% correct about the Map – you definitely should put the Email in the Key and the Id or Contact in the value!
One more suggestion I have for you – the first SOQL query can potentially break! If you have a lot of contacts in your database you’ll get an error. So you only need to query contacts that have emails that at least one of your leads has.
Check this out for more info:
https://www.sfdc99.com/2014/01/20/soql-queries-inside-loops/
Thank you David (-;
Hi Kruthi,
There is another thing I came across, why you only compare the email address? To identify a duplicate you really need to compare the name and the email, because a lot of companies have org wide email addresses means you can have different people from the same company with the same email like info@…..or support @…..
If you now compare name and email you can stick with the ID as key in the map
but you would need to use a loop to go through the map
List conEmailMapLst= [SELECT Id, Name, email from contact];
Map conEmailMap = new map();
with
Map conEmailMap = new Map([SELECT Id, Name, email from contact]);
Because if you have multiple Names with the same email those wouldn’t be in the map as a map does not allow duplicate keys
(Sorry to argue with you all on this point)
for (Lead lEmail: Trigger.New){
for(Contact c: conEmailMap.values())
{
If(lEmail.name == c.Name && lEmail.Email == c.Email)
{
string errMsg = ‘Email already exists’;
lEmail.addError(errMsg);
}
}
this approach is also a highly requirement question
Lets say your employer is a hospital supplier and every doctor can be a lead
The doctor has to request any product interest through the company wide address orders@nicehealth.com
so you can have David , Kruthi, Viru and Andreas ordering products from you with an email orders@nicehealth.com.
Another sample would be an exhibition about marketing tools. and you get a bunch of business cards
One company established for their sales personal the company wide address sales@buyonegetonefree.com
but all some 20 salespeople are interested in your service?
But if you don’t care about this point, then the best is to use the email as the map key and using the contains key() method.
Andreas
Andreas – great note, you are totally correct!
Yes Andreas you have a valid point.
I implemented dedupe in both ways (as per Viru and Andreas inputs) successfully.
Thank you both for your inputs.
David surely will consider your suggestion on SOQL as well. Thanks again.
However i have few questions,
1) Is it good to have for loop within for ?
for (Lead lEmail: Trigger.New){
for(Contact c: conEmailMap.values())
{
If(lEmail.name == c.Name && lEmail.Email == c.Email)
{
string errMsg = ‘Email already exists’;
lEmail.addError(errMsg);
}
}
2) Can you pls explain what is an ‘External Entry Id’ error?
Thank you all once again.
Instead of doing the double for loop, try making the key of you Map c.Name + c.Email =)
No idea what External Entry ID error is!
David
Hi David
I have a strange issue here . I have written the below deduping trigger based on your webinar. I have defined the Dupe contact fields as a text field and have a validation rule on it. I am getting the error while insert/update with a duplicate email id, but the value of the Id field is not getting populated in the Dupe Contact field. What am I missing here ? (So, I am getting a validation error on the Dupe Contact field but the field itself is blank)
trigger DetectDupes on Contact (before update, before insert) {
for (Contact l : Trigger.new) {
if (l.Email != null) {
String contactEmail = l.Email;
list dupeContacts = [Select Email from Contact
where Email = :contactEmail];
if (dupeContacts.size() > 0) {
l.Dupe_contact__c = dupeContacts[0].Id;
} else {
l.Dupe_contact__c = null;
}
} else {
l.Dupe_contact__c = null;
}
}
}
Hello Mithun Sapui,
i think the Dupe_contact__c field is empty because in trigger the record is not committed till you use DML operation like update or insert.
this trigger work fine but when you assign ” I.Dupe_contact__c = dupeContacts[0].Id ” it’ll assigned but not committed that’s why your text field is still empty when record is duplicated.
Am i right David ?
Great one Viru, very close! If this was an after trigger you’d be spot on!
Since it’s a before trigger there is an implied update call automatically at the end of the trigger. It literally means “before update” !
It’s best if you don’t add the Dupe_Contact__c field to the layout and simply have the error message up on the top of the page. Having the field there doesn’t add any value to the user or the admin. But you’re right that the email address won’t show on that field if the validation rule catches it!
Thanks so much, David and Viru. I will do it that way.
Hi David.
I think ,it is not a good option to write SOQL inside loop. So I modified it. Let me know ,is it right?
trigger DuplicateTrigger on Lead (before insert , before update) {
List leadEmail= new List();
for(Lead l:Trigger.new){
leadEmail.add(l.Email);
}
Integer dupContact=[select count() from Contact where Email=:leadEmail];
for(Lead l:Trigger.new){
if(l.Email!=null){
if(dupContact>0){
l.addError(‘Duplicate lead record ‘);
}
}
}
}
hahaha awesome, great observation! I cover how to do this in chapter 5!
Hi David, this is in continuation to my previous reply.
I modified the code with the following to check only when the lead status is not ‘
closed converted’ as this is the status that every lead gets on conversion.
This has fixed the problem for now.
if(leadforDupe.Email != null && leadforDupe.Status ‘Closed – converted’ )
Well done Amitabh!!! Way to hang in there!
Hi David, thanks for all the help, I have moved till chapter 7 successfully.
However when i revisited the code on chapter 4, this is working fine if i update an existing Lead record.
When I create a new Lead and try to convert it , again the validation is hit :-(. as the converted contact will have the same email as that of the lead.
thanks in advance.
/*
not lead a new lead create if the contact exists.
Since leads and contacts are not related
*/
trigger SFDC99_Chap4_LeadDedupe on Lead (before insert, before update) {
for(Lead leadforDupe :Trigger.new){
//checking if the lead has an email
if(leadforDupe.Email != null){
//checking the values of the contacts for emails
List dupe = [SELECT Id,LastName,email FROM Contact where email =: leadforDupe.email];
//if email
if(dupe.size() > 0){
leadforDupe.addError(‘Duplicate value on ‘+ dupe[0].id);
}
}
}
}
Hi David,
I know i have been bugging you with my questions. But i am really enjoying coding:
another one for you: I have tried a little different not sure if this is right. I have done this for contact as i am trying on my developer org and all the licences are consumed so instead of using lead i have used contact.
But it is giving error on the user page when i am trying to deactivate the user:
“Review all error messages below to correct your data.
Apex trigger reassignUser caused an unexpected exception, contact your administrator: reassignUser: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 0039000000VUI8GAAX; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): Contact, original object: User: []: Trigger.reassignUser: line 25, column 1
”
I am clueless what this erro is talkinng about.
code snippet:
trigger reassignUser on User (after update)
{
Set userIds = new Set();
List conList= new List();
List oppList= new List();
List updateCon = new List();
for(User loopUser:trigger.new)
{
if(loopUser.IsActive==false)
{
userIds.add(loopUser.id);
}
}
system.debug(‘@@ UserIds @@’+userIds);
conList=[Select id,OwnerId from Contact where OwnerId in: userIds];
system.debug(‘contact list’+conList);
User sysAdmin= [Select id, name from User where name=’Poornima S’];
system.debug(‘@@system admin @@’+sysAdmin);
for(Contact loopCon:conList)
{
loopCon.OwnerId=sysAdmin.id;
system.debug(‘@@ loop contact @@’+loopCon);
updateCon.add(loopCon);
}
update updateCon ;
system.debug(‘@@ contact list @@’+updateCon );
}
Thanks
This one’s a tough one and can go many directions – try Googling the error message and see where that takes you!
Hi David,
Really thanks for starting this blog. I’ m loving it :)
I used this way of doing the dupeblocker code since i did not want to query inside the for loop as it will query every time the loop is iterated . But i ended up using iterative for loop, which i suppose may be a problem in bulk data. Please suggest how this can be written in a better way.
trigger findDupes on Lead (before insert, before update)
{
List conList = new List();
conList= [Select email,id from Contact];
for(Lead loopLead: Trigger.new)
{
if(loopLead.email!= null)
{
for(Contact loopCon:conList)
{
if(loopLead.email==loopCon.email)
{
String errorMessage = ‘Duplicate contact found! ‘;
errorMessage += ‘Record ID is ‘ + loopCon.Id;
loopLead.addError(errorMessage);
}
}
}
}
}
Cheers!!
Great call!
You definitely need to check out Chapter 5, as it’s entirely dedicated to answering this question you have!
Basically, you need to learn the proper way to bulkify =)
David
Bravo!! Chapter 5 is amazing. I always had a problem in getting this map thing worked out. But now i have got the clear understanding on this.
Hi David,
Thank you for your tutorials. I know nothing about coding, so your tutorials are easy to follow. I want to create a trigger that calculates the # of activity per opportunity. The roll-up summary is very limited in Salesforce and I think a trigger would give me the activity count. Please advise. Thanks in advance for your help.
A common trigger!
Try this:
1. Create a trigger on Tasks
2. Create a set of all Opportunities related to Tasks in your trigger
3. Do one SOQL query to get all Opportunities in #2 and their related tasks
– https://www.sfdc99.com/2013/06/24/example-how-to-write-a-cross-object-soql-query-part-2/
4. For each Opportunity, count the number of related tasks!
You can do it!!!!
But if you’re lazy, you can just download this:
https://appexchange.salesforce.com/listingDetail?listingId=a0N30000009i3UpEAI
=)
Hi David,
Your explanations are crisp and amazing.Its like no other. I love the way you have structured content.
And you are doing such an amazing job not only helping beginners like me learn but also inspire them which makes Salesforce coding such a joy to learn.I cant thank you enough.
I have a doubt, could you please make me understand these two lines of code
1)List userOpps = [select Id from opportunity
where ownerId = :u.id];
2)for(opportunity x:userOpps){x.ownerId = u.ManagerId;}
I understand that Contact and Account also follow the same pattern.
from Andreas’s post.
Thanks a Million
Sandeep
P.S Dav you are the best
My pleasure!
The first line is getting a List of all Opportunities that have a specific owner. The second line goes through each of these opps one by one, then changes their owner to the owner’s manager.
So if I own 5 opps and my manager on my user record is Sandeep, all 5 of my opps will be changed to be owned by Sandeep!
Hope this helps!
David
Just to improve upon the great work, you can also use getLevenshteinDistance method to find similar emails too, so for example you can take the string, strip out the domain(to reduce the Levenshtein’s Distance) and see how many similar records are there with a Distance of 2-5 so you can calculate it for user typo’s or different spelling combinations.
Oh my goodness what a wonderful method! Thank you for showing me!
oki doesn’t work , mixed DML exception (setup / non setup object)
What are you trying to do? =)
Hi David,
Once again — great site — I am really enjoying your work; not just the lessons but the entire site structure and how you have it tied in with WordPress – slick stuff. I am impressed with your energy level! Keep it going! :)
In regard to the code example above and to make sure that I am not misunderstanding things that I thought I knew and continue to learn — would you say that the SELECT query on the contacts should not be inside the trigger.new loop? Would it be best to some how get a map of the contacts prior to the trigger loop? Am I wrong or are you building us up to that and want to keep the lessons learned as basic as possible?
I just want to make sure I am staying on the right track so I appreciate your feedback.
Tom B.
Excellent observation Tom!
I’m building the user up at this point =) You’ll want to zip to Chapter 5 for tutorials on bulkifying code!
David
But above trigger is not working in case of convert lead. So do you have any other way to implement convert Lead also.
This will work when the lead is inserted/updated – so it runs before the lead conversion, which is even better!!
Hi David,
Not sure if you received my last comment as it does not appear to have posted. I wanted to thank you for providing the most easy to understand tutorials for beginners trying to learn apex. Please keep on posting. Also, could you possibly send me some intermediate sample trigger exercises (like the deduping one) so that I can try to create them? I would really appreciate it. I am getting to grips with Apex at a phenomenal rate thanks to this site.
Thank you
Graham
Here’s another trigger to try:
Write a trigger that reassigns all of a User’s leads, accounts, contacts, and opportunities when he/she is deactivated!
David
Hi David, you explanations are the best I’ve ever seen, keep up the great work. I wrote the trigger you suggested that that reassigns all of a User’s leads, accounts, contacts, and opportunities when he/she is deactivated and wanted you to see if I did it correctly.
trigger ReassignUserRecs on User (after update) {
for (User u : trigger.new ){
If (u.isActive = true) {
List userLeads = [select Id from lead
where ownerId = :u.id];
List userContact = [select Id from contact
where ownerId = :u.id];
List userOpps = [select Id from opportunity
where ownerId = :u.id];
If (userLeads != null && userLeads.size() > 0){
for (Integer i = 0; i 0){
for (Integer i = 0; i 0){
for (Integer i = 0; i <= userOpps.size() -1; i++){
// reassign owner to sys admin
userOpps[i].ownerId = '00570000001LL9';
}
update userOpps;
}
}
}
}
Awesome stuff Keith, you are very close! This is incredibly impressive!
Here are some potential areas of improvement:
– I think there is a small error in that you run the trigger if a User is active instead of inactive!
– This may be something wrong with the way comments work on this site, but some of your code might be cut off. I’m talking about the area where you check if leads exists, then iterate across each to reassign them. Ditto for contacts as well! Try re-pasting those lines and I’ll let you know if they look good.
David
Looks this better ? , but not bulkified
trigger ReassignUserRecs on User (after update) {
for (User u : trigger.new ){
If (!u.isActive) {
List userLeads = [select Id from lead
where ownerId = :u.id];
List userContact = [select Id from contact
where ownerId = :u.id];
List userOpps = [select Id from opportunity
where ownerId = :u.id];
List userAccount = [select Id from Account
where ownerId = :u.id];
for(Lead x:userLeads){x.ownerId = u.ManagerId;}
for(contact x:userContact){x.ownerId = u.ManagerId;}
for(opportunity x:userOpps){x.ownerId = u.ManagerId;}
for(Account x:userAccount){x.ownerId = u.ManagerId;}
update userLeads;
update userContact;
update userOpps;
update useraccount;
}
}
}
Hey David,
Please explain that question once again..if u dont mind…i didnt understand the meaning of term “deactivated”….
Thanks,
Srinivas.
By deactivated I simply mean the “Active” checkbox on the User is unchecked =)
But even better than this, try this quiz for this chapter (answers are posted as well)
https://www.sfdc99.com/2014/02/01/quiz-chapter-4/
Hi David,
I tried my hand on writing the reassignment trigger. Below is the code
trigger ReassignRecords on User (after update) {
List accounts = [Select Id from Account where ownerId in: Trigger.new];
List contacts = [Select Id from Contact where ownerId in: Trigger.new];
List leads = [Select Id from Lead where ownerId in: Trigger.new];
List opportunities = [Select Id from Opportunity where ownerId in: Trigger.new];
for(User u : Trigger.new){
if(u.isActive == false){
for(Account a : accounts){
a.ownerId = ‘00590000002jyDnAAI’;
}
for(Contact c : contacts){
c.ownerId = ‘00590000002jyDnAAI’;
}
for(Lead l : leads){
l.ownerId = ‘00590000002jyDnAAI’;
}
for(Opportunity o : opportunities){
o.ownerId = ‘00590000002jyDnAAI’;
}
}
}
update leads;
update opportunities;
update accounts;
update contacts;
}
The code compiles successfully. The thing is whenever I try to test it by making a user inactive, I get this error
MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): Lead, original object: User
I did some goggling and found out that its some sort of logical compartmentalization that Salesforce does by considering user as setup object and the rest of my concerned object as non-setup object. Also the answers community suggested that I use @future annotation to work around this.
I have some info about @future annotation but I would love to know what you thin about this.
Thanks :)
@future isn’t a bad solution but I wonder if you’d have better results using a before update?
Now that you mention it, I’m going to do a post on @future!
David
“Now that you mention it, I’m going to do a post on @future!”
good idea, please do not forget about the implications of the @future / trigger (arguments) and @future and batch