function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kenji775Kenji775 

Preventing System.ListException: Duplicate id in list:

Hello All,

I have a trigger that is a bulk update. Sometimes the same object will make it into my update list more than once, which causes the error

 

UpdateParticipationTrigger: execution of AfterInsert caused by: System.ListException: Duplicate id in list: SomeSalesforceIDHere

 

 My list is built using

 

 

Set<Id> ConIds = new Set<Id> {}; for (Payments__c p : payments) { ConIds.add( p.Contact__c ); }

 

 How can I prevent duplicates from making it into that list, and hence causing errors? I am sure it is very simple to do (basically look in the list to see if the ID we are about to add is already in there, if so, don't add it). Let me know. Thanks!

 

Best Answer chosen by Admin (Salesforce Developers) 
JimRaeJimRae

My error, I thought it said 77, not 73.

My guess is that your looping of the payments is the issue.  Could there be more than one payment for a single contact in your batch?  That would cause the error.

You are getting a contact out of the contact map based on the payment contact (m.get(p1.Contact__c));  If more than one payment in your list had the same contct, you would get duplicates.

What you need to determine is if you need to know if this occurs or not.  If not, you could make the contactupdates a map (which does not support duplicate ID's by the way).  and then loop through the map for your updates

 

Here is your code updated to support that model:

 

 

public class SetLastParticipationDate { //This function gets passed a payment object (or a list of them, and updates the parent contact's //Last_Participation__c field with the payments Payment_Received_Date__c field. //Payments and Contacts are linked by the Contact__c and Payments__c fields respectivly. //Update, this trigger will need to also update the attached contact of any sub respondent that are linked //to any of the respondent records we are updating. Payments are linked to respondent records by the //RespondentRecord__r field. public static void UpdateContactBulk(Payments__c[] Payments) { DateTime dT = System.now(); Date myDate = date.newinstance(dT.year(), dT.month(), dT.day()); //Save the StudyID of the first payment record to a variable since they are all going to have the same ID. String StudyID = Payments[0].ParentStudyID__c; //Create a list of Contact ID's from the payment records Set<Id> ConIds = new Set<Id> {}; for (Payments__c p : payments) { ConIds.add( p.Contact__c ); } //Build the map of contacts from the list above Map<ID, Contact> m = new Map<ID, Contact>([SELECT ID FROM contact WHERE Id in: ConIds ]); //Create a list of contacts so we can save all all the data and update in one push. Map<ID,Contact> contactupdates = new Map<ID,Contact>{}; //Loop over every payment record received. for (Payments__c p1 :Payments) { // Create contact object Contact contacts = m.get(p1.Contact__c); if(p1.Status__c == 'Tested') { // Set the last participation field on the contact we find as the date from payment contacts.Last_Participation__c = p1.Payment_Received_Date__c; } //---------------------------------------------------------------------// //Here I need to add code that finds any sub respondents attached to the //current respondent record (RespondentRecord__r), and update them like I did above //(I must change the Last_Participation__C of the contact) //Something like: // // Find all Sub Respondent Records that are attached to the current payments respondent record (RespondentRecord__r) // From those sub Respondent Records, extract the Contact ID's (the Sub_Respondent__r), and update all those contacts // Last_Participation__c to the Payment_Received_Date__c of the current record. // //Perhaps it would be best just to build a map of Sub Respondent Contact ID's in this loop, and perform the //actual update outside of this loop. I just have to make sure that no duplicate ID's get included in that map. //Also I'm not sure how exactly to get that info (The contact ID off the Sub Respondent Record) //---------------------------------------------------------------------// //If the person is not tested, we need to mark them available for testing again. else { contacts.Available_To_Test_On__c = myDate; contacts.Recently_Tested__c = false; } contactupdates.put(contacts.id,contacts); } //Update all the contacts. update contactupdates.values(); List<CampaignMember> cmupdate = ([SELECT ID,Status FROM CampaignMember WHERE ContactId in: ConIds and CampaignId = :StudyID]); for (CampaignMember cm: cmupdate ) { cm.Status = 'Tested'; } update cmupdate; } }

 

 

 

All Answers

JimRaeJimRae

Are you sure that is where your error is coming from?

A Set is unique by definition, using the technique you show in your code snippet would create a unique list of id's in the ConIDs set.

 

Are you doing a SOQL query using the ConIDs set and then using that result in a list?  That could be where the duplicate would come from.

Kenji775Kenji775

You could be right, I am pretty ignorant when it comes to Apex. The full error is

 

UpdateParticipationTrigger: execution of AfterInsert caused by: System.ListException: Duplicate id in list: 0034000000UefXZAAZ Class.SetLastParticipationDate.UpdateContactBulk: line 73, column 3 Trigger.UpdateParticipationTrigger: line 4, column 5 

 

The full trigger is (You can ignor the large comments block in the center, thats for a modification I want to make to this trigger that does not exist currently)

 

 

public class SetLastParticipationDate { //This function gets passed a payment object (or a list of them, and updates the parent contact's //Last_Participation__c field with the payments Payment_Received_Date__c field. //Payments and Contacts are linked by the Contact__c and Payments__c fields respectivly. //Update, this trigger will need to also update the attached contact of any sub respondent that are linked //to any of the respondent records we are updating. Payments are linked to respondent records by the //RespondentRecord__r field. public static void UpdateContactBulk(Payments__c[] Payments) { DateTime dT = System.now(); Date myDate = date.newinstance(dT.year(), dT.month(), dT.day()); //Save the StudyID of the first payment record to a variable since they are all going to have the same ID. String StudyID = Payments[0].ParentStudyID__c; //Create a list of Contact ID's from the payment records Set<Id> ConIds = new Set<Id> {}; for (Payments__c p : payments) { ConIds.add( p.Contact__c ); } //Build the map of contacts from the list above Map<ID, Contact> m = new Map<ID, Contact>([SELECT ID FROM contact WHERE Id in: ConIds ]); //Create a list of contacts so we can save all all the data and update in one push. list<Contact> contactupdates = new List <Contact>(); //Loop over every payment record received. for (Payments__c p1 :Payments) { // Create contact object Contact contacts = m.get(p1.Contact__c); if(p1.Status__c == 'Tested') { // Set the last participation field on the contact we find as the date from payment contacts.Last_Participation__c = p1.Payment_Received_Date__c; } //---------------------------------------------------------------------// //Here I need to add code that finds any sub respondents attached to the //current respondent record (RespondentRecord__r), and update them like I did above //(I must change the Last_Participation__C of the contact) //Something like: // // Find all Sub Respondent Records that are attached to the current payments respondent record (RespondentRecord__r) // From those sub Respondent Records, extract the Contact ID's (the Sub_Respondent__r), and update all those contacts // Last_Participation__c to the Payment_Received_Date__c of the current record. // //Perhaps it would be best just to build a map of Sub Respondent Contact ID's in this loop, and perform the //actual update outside of this loop. I just have to make sure that no duplicate ID's get included in that map. //Also I'm not sure how exactly to get that info (The contact ID off the Sub Respondent Record) //---------------------------------------------------------------------// //If the person is not tested, we need to mark them available for testing again. else { contacts.Available_To_Test_On__c = myDate; contacts.Recently_Tested__c = false; } contactupdates.add(contacts); } //Update all the contacts. update contactupdates; List<CampaignMember> cmupdate = ([SELECT ID,Status FROM CampaignMember WHERE ContactId in: ConIds and CampaignId = :StudyID]); for (CampaignMember cm: cmupdate ) { cm.Status = 'Tested'; } update cmupdate; } }

 

 

 

 You could very well be right, I just kinda jumped to conclusions. Probably a bad call on my part :P. Thanks for the help.

JimRaeJimRae

My guess would be the that the duplicate is in the cmupdate list, but I am not sure how it would be getting there.  Each contact should only be allowed to be added to a campaign once (hence only one campaignmember entry).

you might have to do some digging on your test data to see if that condition could somehow occur, and if so, you would need to change your code to prevent it.

 

Kenji775Kenji775

Well I can tell you for a fact (at least through empirical evidence) that this error only occures when I feed in data has the same contact listed more than once. It does say the error is coming from line 73, which is the

 

update contactupdates;  

 

line. Which is before anything witht the campaign members. So knowing that it only happens when there are duplicate contacts being passed in, somehow I am fairly sure that the set is ending up with duplicates. I think actually, the map

 

Map<ID, Contact> m = new Map<ID, Contact>([SELECT ID FROM contact WHERE Id in: ConIds ]); is what is getting the duplicates. Maps can have dupes right? So that would explain it. Though I though the SOQL would only return each ID once. I dunno, I'm confused. Anyone else have any ideas?

JimRaeJimRae

My error, I thought it said 77, not 73.

My guess is that your looping of the payments is the issue.  Could there be more than one payment for a single contact in your batch?  That would cause the error.

You are getting a contact out of the contact map based on the payment contact (m.get(p1.Contact__c));  If more than one payment in your list had the same contct, you would get duplicates.

What you need to determine is if you need to know if this occurs or not.  If not, you could make the contactupdates a map (which does not support duplicate ID's by the way).  and then loop through the map for your updates

 

Here is your code updated to support that model:

 

 

public class SetLastParticipationDate { //This function gets passed a payment object (or a list of them, and updates the parent contact's //Last_Participation__c field with the payments Payment_Received_Date__c field. //Payments and Contacts are linked by the Contact__c and Payments__c fields respectivly. //Update, this trigger will need to also update the attached contact of any sub respondent that are linked //to any of the respondent records we are updating. Payments are linked to respondent records by the //RespondentRecord__r field. public static void UpdateContactBulk(Payments__c[] Payments) { DateTime dT = System.now(); Date myDate = date.newinstance(dT.year(), dT.month(), dT.day()); //Save the StudyID of the first payment record to a variable since they are all going to have the same ID. String StudyID = Payments[0].ParentStudyID__c; //Create a list of Contact ID's from the payment records Set<Id> ConIds = new Set<Id> {}; for (Payments__c p : payments) { ConIds.add( p.Contact__c ); } //Build the map of contacts from the list above Map<ID, Contact> m = new Map<ID, Contact>([SELECT ID FROM contact WHERE Id in: ConIds ]); //Create a list of contacts so we can save all all the data and update in one push. Map<ID,Contact> contactupdates = new Map<ID,Contact>{}; //Loop over every payment record received. for (Payments__c p1 :Payments) { // Create contact object Contact contacts = m.get(p1.Contact__c); if(p1.Status__c == 'Tested') { // Set the last participation field on the contact we find as the date from payment contacts.Last_Participation__c = p1.Payment_Received_Date__c; } //---------------------------------------------------------------------// //Here I need to add code that finds any sub respondents attached to the //current respondent record (RespondentRecord__r), and update them like I did above //(I must change the Last_Participation__C of the contact) //Something like: // // Find all Sub Respondent Records that are attached to the current payments respondent record (RespondentRecord__r) // From those sub Respondent Records, extract the Contact ID's (the Sub_Respondent__r), and update all those contacts // Last_Participation__c to the Payment_Received_Date__c of the current record. // //Perhaps it would be best just to build a map of Sub Respondent Contact ID's in this loop, and perform the //actual update outside of this loop. I just have to make sure that no duplicate ID's get included in that map. //Also I'm not sure how exactly to get that info (The contact ID off the Sub Respondent Record) //---------------------------------------------------------------------// //If the person is not tested, we need to mark them available for testing again. else { contacts.Available_To_Test_On__c = myDate; contacts.Recently_Tested__c = false; } contactupdates.put(contacts.id,contacts); } //Update all the contacts. update contactupdates.values(); List<CampaignMember> cmupdate = ([SELECT ID,Status FROM CampaignMember WHERE ContactId in: ConIds and CampaignId = :StudyID]); for (CampaignMember cm: cmupdate ) { cm.Status = 'Tested'; } update cmupdate; } }

 

 

 

This was selected as the best answer
thecrmninjathecrmninja

Jim,

 

I wanted to be sure to reply to this to confirm for other people that come across this issue that this is indeed an effective fix.  

 

I had a trigger on the Task object that looked at Task for Leads and updated values on the Lead object.  When recurring Tasks were entered against a Lead, my List-Based DML methodology was generating this issue.  Changing to a map-based DML approach resolved my problem.

 

Did have.....

 

trigger LeadStating on Task (after insert){
//List for recieiving WhoID values of tasks triggering trigger
    List<ID> leadStatsIDs = New List<ID>();

//String value that will recieve whoid to ensure trigger only fires for Tasks related to Leads
Public String LeadURL;
Public String subjectval=Trigger.new[0].subject;
Public String LeadURL2=Trigger.new[0].whoid;

//Run through Tasks in trigger, for those associated to Lead, add to above List
for (Task t:System.Trigger.new){
if ((subjectval.contains('Call')||subjectval.contains('CALL'))&&t.whoid<>Null&&t.IsRecurrence == False) 
{
                                    System.debug('t.whoId1: '+t.whoId);
      LeadURL = String.valueof(t.whoid);
      subjectval=String.valueof(t.Subject);
        if(LeadURL.startsWith('00Q')&&LeadURL<>Null&&t.IsRecurrence == False)
                    leadStatsIDs.add( t.whoId ) ;
     }
}

//Build Lists and Maps.  Maps will be used in below "For" Loops and the Lists will receive the Loops' result (Leads with updated values)
List<lead> leadUpdate = new List<lead>();//NOTE: LINE THAT WAS CREATING MY ISSUE - USING A LIST ALLOWED FOR DUPLICATE id INCLUSION
   Map<ID,Lead> M = new Map<ID, Lead>([select Id,Last_Contact__c from lead where Id in :leadStatsIDs Limit 1]);

/*My goal for the following For loop is to compare a date field from the lead against a date field on the task.
*If the Task date field is older than the Lead field, update the Lead field with the Task value.*/
  for(Task oTask : Trigger.new){
        if(m.containsKey(oTask.WhoId)){
            Lead tmpLead = m.Get(oTask.WhoId);
{               if((tmpLead.Last_Contact__c < oTask.ActivityDate || tmplead.Last_Contact__c == Null)&&oTask.Campaign_ID__c==Null)
                    tmpLead.Last_Contact__c = oTask.ActivityDate;
                    leadUpdate.add(tmpLead);//NOTE: LINE THAT WAS CREATING MY ISSUE B/C IT ADDED DUPE IDS
            }    
        }
    }
Update LeadUpdate;//NOTE: LINE REFERENCED IN MY APEX SYSTEM EXCEPTION MESSAGE
}

 

 

Changed to.....

 

 

trigger LeadStating on Task (after insert)

{//COMMENT: List for recieiving WhoID values of tasks triggering trigger
    List<ID> leadStatsIDs = New List<ID>();

//COMMENT: String value that will recieve whoid to ensure trigger only fires for Tasks related to Leads
Public String LeadURL;
Public String subjectval=Trigger.new[0].subject;
Public String LeadURL2=Trigger.new[0].whoid;
 
//COMMENT: Run through Tasks in trigger, for those associated to Lead, add to above List
for (Task t:System.Trigger.new)
{//COMMENT: I only am concerned with Call Tasks
if ((subjectval.contains('Call')||subjectval.contains('CALL'))&&t.whoid<>Null&&t.IsRecurrence == False) 
{      LeadURL = String.valueof(t.whoid);
      subjectval=String.valueof(t.Subject);
//COMMENT: Confirm that this Task is a Lead Task
        if(LeadURL.startsWith('00Q')&&LeadURL<>Null&&t.IsRecurrence == False)
                    leadStatsIDs.add( t.whoId ) ;
     }
}

//COMMENT: Various Maps.  SOQL Maps will be used in below "For" Loops and Non-SOQL maps will receive the Loops' result (Leads with updated values)
        Map<ID,lead> leadUpdate = new Map<ID,lead>{}; //NOTE: NOW, WE ARE INSTANTIATING A MAP, INSTEAD OF A LIST
   Map<ID,Lead> M = new Map<ID, Lead>([select Id,Last_Contact__c from lead where Id in :leadStatsIDs Limit 1]);

/*COMMENT: My goal for the following For loop is to compare a date field from the lead against a date field on the task.
*If the Task date field is older than the Lead field, update the Lead field with the Task value.*/
  for(Task oTask : Trigger.new){
        if(m.containsKey(oTask.WhoId)){
            Lead tmpLead = m.Get(oTask.WhoId);
{               if((tmpLead.Last_Contact__c < oTask.ActivityDate || tmplead.Last_Contact__c == Null)&&oTask.Campaign_ID__c==Null)
                    tmpLead.Last_Contact__c = oTask.ActivityDate;
            leadUpdate.put(tmpLead.id,tmpLead);//NOTE: NOW, WE PASS VALUES TO THE MAP INSTEAD OF A LIST.  NOTICE THAT THIS IS A 'PUT' METHOD INSTEAD OF AN 'ADD' METHOD
            }    
        }
    }

Update LeadUpdate.values();//NOTE: NOW, WE PERFORM OUR DML STATEMENT AGAINST A MAP.  NOTICE THAT '.VALUES()' APPENDS THE DML STATEMENT, UNLIKE THE PREVIOUS LIST STATEMENT
}

 

 

If, like me, you encountered this issue.  Please refer to how I have changed the apex code above.  I have made comments starting with '//NOTE:' everywhere that a change was made, so that it is easy to reference how to change your own code.

 

Thanks Jim!

Pankaj KPankaj K

it also worked for me..Thank You..

KitagawaSan1337KitagawaSan1337

I have this trigger that is causing a similar issue occationally when my users use the 'email to salesforce' functionality: 

 

trigger TaskUpdateAccountdate on Task (after insert, after update) {

List<Account> accsToUpdate = new List<Account>();

for(Task tsk : trigger.new)

{if ((tsk.IsClosed && trigger.isInsert && tsk.AccountId!=null) || (tsk.AccountID!=null && tsk.IsClosed && !trigger.oldMap.get(tsk.id).IsClosed))
accsToUpdate.add(new Account(Id = tsk.AccountId, Last_Activity_Date__c = Date.today()));
}

Update accsToUpdate;

}

 

Would I have to do something similar to prevent this? 

 

Thanks!! 
Justin  

KitagawaSan1337KitagawaSan1337

Was able to fix using similar code, thanks! 

 

trigger TaskUpdateAccountdate2 on Task (after insert, after update) {
List<ID> AccountIds = New List<ID>();

Public String TskAccID = Trigger.new[0].accountid;

for (Task t:System.Trigger.new)
{
if(TskAccID != null)
AccountIds.add(t.accountid);
}

Map<ID,Account> accsToUpdate = new Map<ID,Account>();
Map<ID,Account> m = new Map<ID,Account>([SELECT Id, Last_Activity_Date__c FROM Account WHERE id in :AccountIds Limit 1]);

for(Task tsk : trigger.new)
if(m.containsKey(tsk.AccountId)){
  Account tmpAcc = m.get(tsk.AccountId);
  
if ((tsk.IsClosed && trigger.isInsert && tsk.AccountId!=null) || (tsk.AccountID!=null && tsk.IsClosed && !trigger.oldMap.get(tsk.id).IsClosed)){
tmpAcc.Last_Activity_Date__c = Date.today();

accsToUpdate.put(tmpAcc.id,tmpAcc);


}

Update accsToUpdate.Values();

}}

 

JulesAusJulesAus

very helpful for me too. Thanks!

Amit VaidyaAmit Vaidya
Thank you very much Jim... you have resolved my bigger issue..