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
bpolbpol 

bulk trigger works on individual record but shows incorrect data on bulk upload

I've written my first bulk-ready trigger!! 

 

It works -- that is, it doesn't break and it updates with the correct data -- when editing a single record!!

 

But it doesn't work: specifically when I bulk upload records, the records upload, but the data generated by the trigger is _very_ incorrect, some is missing.  Not sure what happened.  Interestingly when I edit one of these uploaded records, and re-save the record, it looks good.

 

Help!

 

Here's the trigger... (it's a bit long)

 

>> The three fields I am trying to update are Record_Type__c, Related_School__c, and Related_District__c

>> I key off of Dept_Seg_1__c for Record_Type__c

>> I also key off of Dept_Seg_2__c and Dept_Seg__3__c which in combination can relate back to an Account which leads to the Record_School__c and Record_District__c

 

trigger Bulk_Labor_Before on Labor__c (before insert, before update) {

// Loop through all of the Labor Records and collect necessary lists
//------------------------------------------------------------------
list<Labor__c> thelabor = new list<Labor__c>(); // list contains all of the labor being processed

map<String, String> map_ltype_to_typename = new map<String, string> {
'201' => 'mh center', '310' => 'staffing', '410' => 'sales',
'510' => 'marketing', '610' => 'training', '710' => 'it',
'810' => 'accounting & finance', '820' => 'hr',
'110' => 'call center', '411' => 'recruiting', '150' => 'online - coaching',
'101' => 'onsite - coaching', '102' => 'onsite - coaching', '103' => 'onsite - coaching',
'104' => 'onsite - coaching', '105' => 'onsite - coaching', '106' => 'onsite - coaching',
'' => ''};
map<id, String> map_lId_to_typename = new map<id, String> ();

list<string> list_districtschool = new list<string>();
list<string> list_schools = new list <string>();
map<id, String> map_lId_to_districtschool = new map<id, string>();
map<id, string> map_lId_to_districtORschool = new map <id, string>();
map<String, id> map_districtschool_accountid = new map<String, id>();
map<String, id> map_districtschool_accountid_parent = new map<String, id>();
map<string, id> map_school_accountid = new map <String, id>();
map<string, id> map_school_accountid_parent = new map <String, id>();

String temp_recordtype = null; // temporary, working variable
String temp_districtcode = null, temp_schoolcode = null, temp_dscode=null;
String dORs = null;

for (Labor__c l :trigger.new) {
thelabor.add(l); // add labor to the main list

temp_recordtype = map_ltype_to_typename.get(l.Dept_Seg_1__c); // set map for general cases

if (l.Dept_Seg_1__c == '150') { // set map to exceptions
if (l.Dept_Seg_2__c == '00004') (temp_recordtype = 'online - tech support');
if (l.Dept_Seg_2__c == '00002') (temp_recordtype = 'online - admin');
if (l.Dept_Seg_2__c == '20000') (temp_recordtype = 'online - admin');
if (l.Dept_Seg_3__c == '0000001') (temp_recordtype = 'online - admin');
if (l.Dept_Seg_3__c == '0000005') (temp_recordtype = 'online - admin');
}
map_lId_to_typename.put(l.id, temp_recordtype);


temp_districtcode = string.valueOf(l.Dept_Seg_2__c);
temp_schoolcode = string.valueOf(l.Dept_Seg_3__c);
if (temp_schoolcode == null){
(temp_schoolcode = '0000000');
}

if (temp_districtcode == null) {
if (temp_schoolcode == '0000000') (temp_schoolcode = '');
dORs = 'S';
temp_dscode = temp_schoolcode;
list_schools.add(temp_schoolcode); // create list of accounts with no district code
}

if (temp_districtcode != null) {
dORs = 'D';
temp_dscode = temp_districtcode + temp_schoolcode;
list_districtschool.add(temp_dscode); // create list of accounts with a district

// code (and possibly a school code)
}

map_lId_to_districtORschool.put(l.id,dORs);

map_lId_to_districtschool.put(l.id,temp_dscode);

}

list <Account> the_districtschoolaccounts = ([select id, parentid, Account_DS_code__c, Account_D_code__c, Account_S_code__c from Account
where Account_DS_Code__c IN :list_districtschool]); // create list of accounts with district codes
list <Account> the_schoolaccounts = ([select id, parentid, Account_DS_code__c, Account_D_code__c, Account_S_code__c from Account
where Account_S_Code__c IN :list_schools]); // create list of accounts w school codesonly

// Create a map of the labordistricts and schools and associated account ids
for (Account a :the_districtschoolaccounts) {
map_districtschool_accountid.put(a.Account_DS_code__c, a.id);
map_districtschool_accountid_parent.put(a.Account_DS_code__c, a.parentid);
}
for (Account a :the_schoolaccounts) {
map_school_accountid.put(a.Account_S_code__c, a.id);
map_school_accountid_parent.put(a.Account_S_code__c, a.parentid);
}

// Loop through all of the Labor Records and insert relevant data
// --------------------------------------------------------------
for (Labor__c l :thelabor){
l.Record_Type__c = map_lid_to_typename.get(l.id);
if (map_lId_to_districtORschool.get(l.id)=='D') {
l.Related_School__c =

map_districtschool_accountid.get(map_lId_to_districtschool.get(l.id));
l.Related_District__c =

map_districtschool_accountid_parent.get(map_lId_to_districtschool.get(l.id));
}
if (map_lId_to_districtORschool.get(l.id)=='S') {
l.Related_School__c =

map_school_accountid.get(map_lId_to_districtschool.get(l.id));
l.Related_District__c =

map_school_accountid_parent.get(map_lId_to_districtschool.get(l.id));
}
}

}

 

 

 

 

 

Message Edited by bpol on 02-24-2010 01:49 AM
Message Edited by bpol on 02-24-2010 02:06 AM
bpolbpol

One error I had was (not sure if related)

 

 DML statment cannot operate on trigger.new or trigger.old

 

jkucerajkucera

DML statement = insert, update or delete, which you don't have in your trigger above so I'm guessing you've made changes since.

 

You can change fields in trigger.new in a before insert, before udpate trigger but can't then call Update explicitly.

 

You have a lot going on in that trigger. I couldn't tell 100%, but I'm guessing you don't need all those maps & lists.  Instead, try to use 2-3 queries to get all the info you need & use a map on the sObject instead of the individual fields.  

bpolbpol
 In terms of the maps v. queries -- I was a bit concerned that doing several queries for one record within a for-statement might cause the trigger to work improperly when bulk-loading records... so I tried to avoid it, using maps instead.

 

Are there certain limits (the governor's limits in laymans-terms) that I should be aware of when using queries?

 

Also are there certain issues I should be aware of when using maps, sets, or lists that would cause 1 record update to work fine, but the bulk upload to work sporadically?  Do I need to clear the lists, or are they reset each time?  Do I need to use sets not lists?

 

PS - You're right.  I did get rid of the update at the end.

jkucerajkucera

The limits that affect you for this:

  • 20 SOQL queries / transaction (in other words - don't put a query in a loop)
  • Map size limited to 1,000 elements pre-Spring '10, post Spring '10 I believe there is no limit to map size
  • However,for post-Spring '10, heap size will be the practical limit for map's as if you have 50Mil items in a map, you'll reach the heap limit


In general, I tend to do something like this:

  1. Loop through Trigger.new & throw the relevent info in a list (either list of leads, list of lead id's, etc)
  2. Use the list to query related objects to populate another list
  3. Loop through the 2nd list to create a map of LeadID to related object
  4. Loop again through Trigger.new to add the related info to Lead using the map from #3
There may even be more efficient ways, but this tends to make things relatively simple for me when coding.
bpolbpol

Teriffic advice about the steps!!  Some of the best advice along with this example: http://sfdc.arrowpointe.com/2008/09/13/bulkifying-a-trigger-an-example/ 

 

I think I'm close.

 

How would create a test to the following?

 

I add all of the reference records in other objects and then 200 records in the student_call_log__c object with a loop.... but I still have limited coverage...

 

 

trigger Bulk_CallLogs on Student_Call_Log__c (after delete, after update) { // 1. Loop through Trigger.new & throw the relevent info in a list (either list of leads, list of lead id's, etc) // 2. Use the list to query related objects to populate another list // 3. Loop through the 2nd list to create a map of LeadID to related object // 4. Loop again through Trigger.new to add the related info to Lead using the map from #3 list<Student_Call_Log__c> list_thelogs = new list<Student_Call_Log__c>(); // list contains all of the logs being processed // Step 1 set <id> list_relatedstudents_temp = new set <id> (); for (Student_Call_Log__c log :list_thelogs) { list_relatedstudents_temp.add(log.related_student__c); } list<Student__c> list_relatedstudents = ([select id from Student__c where id IN :list_relatedstudents_temp]); // Step 2 list<Student_Call_Log__c> list_relatedlogs = ([select id, related_student__c, date__c from Student_Call_Log__c where Id IN :list_thelogs order by Date__c DESC ]); list<Student_Call_Log__c> list_relatedlogs_mostrecent = ([select id, related_student__c, date__c from Student_Call_Log__c where Id IN :list_thelogs order by Date__c DESC limit 1 ]); map<id, id> map_students = new map<id, id>(); map<id, date> map_log_date = new map<id, date>(); map<id, String> map_log_calltype = new map<id, string>(); map<id, String> map_log_callresult = new map<id, string>(); map<id, String> map_log_note = new map<id, string>(); map<id, Integer> map_log_counter = new map <id, integer>(); Integer counter = 1; //Step 3-A for (Student__c s :list_relatedstudents) { for (Student_Call_Log__c log :list_relatedlogs) { map_students.put(s.id, log.id); map_log_date.put(log.id, log.Date__c); map_log_calltype.put(log.id, log.Call_Type__c); map_log_callresult.put(log.id, log.Call_Results__c); map_log_note.put(log.id, log.note__c); map_log_counter.put(log.id, counter); } } //Step 4-A String temp_lognote = null; counter = 0; for (Student__c s :list_relatedstudents) { for (Student_Call_Log__c log :list_relatedlogs) { temp_lognote = temp_lognote + map_log_date.get(log.id) + ' -- ' + map_log_calltype.get(log.id) + ' -- ' + map_log_callresult.get(log.id) + ' -- ' + map_log_note.get(log.id) + '\n'; counter = counter + map_log_counter.get(log.id); } s.Tech_Support_Logs__c = temp_lognote; s.Call_Count__c = counter; temp_lognote = null; counter = 0; } map<id, id> map_students_mostrecent = new map<id, id>(); map<id, string> map_log_mostrecentnote = new map<id, string>(); //Step 3-B for (Student__c s :list_relatedstudents) { for (Student_Call_Log__c log :list_relatedlogs_mostrecent) { map_students_mostrecent.put(s.id, log.id); map_log_mostrecentnote.put(log.id, log.note__c); } } //Step 4-B for (Student__c s :list_relatedstudents) { for (Student_Call_Log__c log :list_relatedlogs_mostrecent) { s.Most_Recent_Call_Note__c = map_students_mostrecent.get(log.id); } } }

 

 

 

jkucerajkucera

Glad to hear you were able to bulkify it! 

 

In general, my tests tend to be more lines of code than the trigger itself as you'll want to test multiple scenarios to ensure it works right.

 

For your example, you'll want to create some Student_Call_log__c and Student__c with all the relevent data you're putting into the maps.  

 

Click on the Test Class and click Run Test, and after running, you can click on the results to see exactly which lines of code were called & which weren't.  That makes it a lot easier to figure out what else you need to add to your test code.

bpolbpol

So here's the test I now have... but it only cover's 51%.

 

Thoughts?  I have a scenario for updates and one for inserts... but I can't get it to budge from 51%....

 

Thanks for your help.

 

 

// TEST INSERT List<Student_Call_Log__c> calllogs = new List<Student_Call_Log__c>(); for(Integer j = 0; j < 200; j++){ Student_Call_Log__c testcall = new Student_Call_Log__c (Note__c='hello'+ j, Date__c = System.today(), Call_Type__c = 'Assessment', Call_Results__c ='ok', Caller_Name__c = 'AVANZA', Related_Student__c = teststudent.Id); calllogs.add(testcall); } test.startTest(); // Start the test, this changes governor limit context to that of trigger rather than test. insert calllogs; // Insert the Log records that cause the trigger to execute. test.stopTest(); // Stop the test, this changes limit context back to test from trigger. List<Student_Call_Log__c> insertedlogs = [SELECT Date__c, Note__c FROM Student_Call_Log__c WHERE Id IN :calllogs]; // Query the database for the newly inserted records. //TEST UPDATE list<Student_Call_Log__c> calllogsupdate = ([select id, note__c from Student_Call_Log__c order by Date__c DESC limit 200]); String temp_note = null; list<Student_Call_Log__c> updatelist = new list <Student_Call_Log__c>(); for (Student_Call_Log__c k :calllogsupdate) { temp_note = k.note__c + 'update'; k.note__c = temp_note; updatelist.add(k); } test.startTest(); // Start the test, this changes governor limit context to that of trigger rather than test. update updatelist; // Update the Log records that cause the trigger to execute. test.stopTest(); // Stop the test, this changes limit context back to test from trigger. List<Student_Call_Log__c> insertedlogs2 = [SELECT Date__c, Note__c FROM Student_Call_Log__c WHERE Id IN :calllogsupdate]; // Query the database for the newly updated records.

 

 

 

jkucerajkucera

Make a bunch of Student__c so that lines like this will be checked:

 

list<Student__c> list_relatedstudents = ([select id from Student__c where id IN :list_relatedstudents_temp]);