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
KCali12KCali12 

Using map to insert records with unique field combinations

I've never used a map in an Apex class before, but I think this scenario requires one, and I need help.

I have a custom object called Time_Allocation__c with the following fields:
  • Unit_Subunit__c (Formula field - string)
  • Time_Sheet_Allocated_Hrs__c (Lookup to to Time_Sheet_Allocated_Hrs__c object)
  • Time_Sheet__c (Lookup to Time_Sheet__c object)

With my batch Apex, which is querying all Time_Allocation__c records where the Time_Sheet_Allocated_Hrs__c field is null, I need to do the following:
  1. Create one Time_Sheet_Allocated_Hrs record for each Time_Sheet__c/Unit_Subunit__c combination.
  2. Populate the Time_Allocation__c record with the appropriate Time_Sheet_Allocated_Hrs__c lookup field (based on the appropriate Time_Sheet_c/Unit_Subunit__c combo.
Here is my class so far:
 
global class TSAllocatedHrsBatch implements
    
    Database.Batchable<sObject>{
        
        global Database.QueryLocator start(Database.BatchableContext bc){
        	return Database.getQueryLocator(
        	[SELECT Id, Time_Sheet_Allocated_Hrs__c, Time_Sheet__c, Unit_Subunit__c  from Time_Allocation__c where 
         	Type__c = 'Program Time' AND Time_Sheet_Allocated_Hrs__c = null order by Time_Sheet__c]);
    }
        
        global void execute(Database.BatchableContext bc, List<Time_Allocation__c> batchList){
		

      		Set<Id>taIds = new Set<Id>();
            Set<String>UnitSubList = new Set<String>();
            Set<Id>TimeSheetIds = new Set<Id>();
         
            
            for (Time_Allocation__c ta : batchList){
                	taIds.add(ta.Id);
                	UnitSubList.add(ta.Unit_Subunit__c);
                	TimeSheetIds.add(ta.Time_Sheet__c);
                	
             Map<Id,String> tsAllHrsMap = new Map<Id, String>();
                tsAllHrsMap.put(ta.Time_Sheet__c, ta.Unit_Subunit__c);
                       
            }
                       
            //Now what?

      
            List<Time_Sheet_Allocated_Hrs__c> tsAllHrsListInsert = new List<Time_Sheet_Allocated_Hrs__c>();

            
            insert tsAllHrsListInsert;
            }
            
                        
    	global void finish(Database.BatchableContext bc) {
     
    }
    }


After putting the values I want in the map, how do pick out the values to create the individual Time_Sheet_Allocated_Hrs__c records that I want to insert?  Again, I only want one record for each Time_Sheet_c/Unit_Subunit__c combination, so I also need to figure out how to avoid creating duplicates.

Do I need a map of a map to do this?  If so, what syntax do I use?

Thanks for any guidance you can offer.

 
Andrew GAndrew G
Hi KCali

you are on the write track,  but with the Map use a key that is Time_sheet_Id and Unit_Subunit.

The last post in this thread has some code which should point you in the right direction.
https://developer.salesforce.com/forums/ForumsMain?id=9062I000000Xy3iQAC

What I am assuming is that each Time_Allocation__c record is unique based on Time Sheet and Unit Subunit.

However, using Maps in this way may not exclude duplication of Time_Sheet_Allocated_Hrs records based on Timesheet and UnitSubunit as it would not check for duplicates across different batch runs for the code.

Also, your code here would actually clear the map on each loop and at the end of the loop you would have just one record in the map.  In this instance, you would have the Map declared outside the loop.
for (Time_Allocation__c ta : batchList){
                	taIds.add(ta.Id);
                	UnitSubList.add(ta.Unit_Subunit__c);
                	TimeSheetIds.add(ta.Time_Sheet__c);
                	
             Map<Id,String> tsAllHrsMap = new Map<Id, String>();
                tsAllHrsMap.put(ta.Time_Sheet__c, ta.Unit_Subunit__c);
                       
            }
Also, if we consider the behaviours of Map, that Map would only have one record per timesheet, as the timesheet would be the unique key and a second entry with the same timesheet would overwrite the value in the map.

Consider 3 Time_Allocation__c records with values
Timesheet 1 + Subunit A
Timesheet 2 + Subunit B
Timesheet 1 + Subunit C

Since "timesheet #" is the unique key, the Map (even if declared outside the loop), would contain two entries.
Timesheet 2 , Subunit B
Timesheet 1, Subunit C (since the value of Subunit A would have been overwritten due to the same key).

I hope the above proves helpful

Reagards
Andrew
KCali12KCali12
Hi, Andrew.  I appreciate your response.  I have been thinking through the example you provided, but my scenario, although similar, is not quite the same.  

Unfortunately, the Time_Allocation__c record is NOT unique based on Time Sheet and Unit_Subunit.  Per time sheet, I will usually have 10 time allocations for that time sheet/unit_subunit combo, essentially one for each day in the pay period.  

I have a flow that, upon edit of a Time_Allocation__c record where the Time_Sheet_Allocated_Hrs__c lookup field is null, will query if a Time Sheet Allocated Hrs with that unique Time Sheet/Unit_Subunit combination exists ... and if it doesn't, it will create one and relate ... if it does, it will just simply relate that record to my Time Allocation.  That totally works on an individual basis, but it does not work, obviously, when a bunch of time allocation  records are edited or inserted at once.  Even if I trigger that flow in a batch class with a size of 10, for example, I am getting duplicate Time Sheet Allocated Hrs records for each Time Sheet/UnitSubunit combination.  If I can't figure out how to write a proper Apex class to make this happen, I have a thought to trigger that flow in a batch class with a size of 1 and run it over night, but I know that's a really bad way to do this.  :)



 
Andrew GAndrew G

Hi KCali

Here is what i think your data diagram looks like: (pardon it's crudeness).
User-added image

And the process:

For a given Timesheet (TS), which I assume is a reflection of a fortnight's (FN) work (possbily for a given employee) , there can be 1 or more Timesheet Allocation Hours (TSAH) and 1 or more Timesheet Allocations (TA).

A TSAH has a field to indicate a SubUnit (which i interpret to be a Type of work e.g Admin, Development, Design)  This is used so that the fortnight TS you can break down Admin work versus Dev Work.

A user will create a TA in which they select only the TS (potentially their own) and the Subunit (e.g Development).  On save, you want to populate the TSAH field, by either creating a new record or finding an existing record.

I am assuming this model as you will roll up the summary of hours into the TS and the TSAH for payroll and reporting purposes (respectively).

If the above is correct, with exception to some of my wording in my previous post, the logic in that post should work.  

here is the Psuedo code which i think should solve the issue.

(loop) for a list of TAs where the TSAH is blank
    build a list of TS Ids
    build a list of Subunits keywords
End loop

Using Ids and Subunits do a query of TSAH using the IN keyword for both lists

(loop) the list of TSAHs from the query
    build a Map using TS Id and Subunit as the Key (this would identify unique TSAHs based on Timesheet and Subunit)

(loop) the original list of TAs
    If TSAH is blank
        if Map contains key TA.Ts.Id and TA.subunit, 
            associate TA with TSAH.
            add TA to list of TAs to just update  (list TAsToUpdate)
        else
            add the TA to a list of TAs where we will bulk create the TSAHs at the end (list TAsWithoutTSAH)
        End if
    End if
End loop

update  TAsToUpdate

(loop)  TAsWithoutTSAH
    create new record TSAH and add to TSAH list
end loop
Insert TSAH list

(loop) TSAH list to get Ids

query TSAH using Ids from previous loop
build another map as before : using TS Id and Subunit as the Key

Loop list TAsWithoutTSAH
   using Key, get record from Map and associate with record

Update TAsWithoutTSAH
 

Noting the above, I would look to build some methods to handle the repetitive stuff, like building the Map and you could also do one for the assigning of TSAH to TAs (pass the List and the Map to the method)


Regards
Andrew