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
paddingtonpaddington 

Map<ID,ID> without looped SOQL

Hi there,

 

So I think that I kind of understand Maps, in that if you create a map with a SOQL query, such as

 

 

Map<ID,sObject> sObMap = new Map<ID,sObject>([SELECT *fields* FROM sObject WHERE *conditions*]);

 you get the key/value pairing of an sObject ID and the sObject itself. I'm slightly unsure as to the purpose of the *fields* in the SOQL query, but it seems to work where I need it to, namely in specifying an sObject from a list of IDs.

 

Today I was trying to create an <ID,ID> map, so I can take a list of accounts and iterate through them to get a key/value pairing for the IDs of a set of Master-Detail related sObjects (Clent_Form__c). I want to insert a set of a third object (Assessments__c), which has lookups to the Account and the related Client_Form__c. The only way I think I can do this is by putting SOQL in a FOR loop (METHOD 1 below), which I know is bad practice for bulk triggers.

 

Is there a better way of taking a list of Accounts and mapping them to detail sObjects?

 

Here's the working (messy) class as it stands.

 

 

public with sharing class createAssessments { public static void IEF(List<Account> accs){ //Declare owner ID variable for IEF Assessments queue Id ownerId = [Select q.Queue.Id from QueueSobject q where q.Queue.Name = 'Application Screenings' and q.SobjectType = 'Assessment__c'].Queue.Id; //Create a list of assessments to be inserted List<Assessment__c> assessments = new List<Assessment__c> (); //METHOD 1 - works but I've got SOQL in a loop //Create map of Account ID => Client Form ID Map<ID,ID> IEFs = new Map<ID,ID>(); for (Account a:accs){ IEFs.put(a.Id,[SELECT Id FROM Client_Form__c WHERE Account__c = :a.Id].Id); } //METHOD 2 - doesn't work as I'm putting sObject Client_Form__c into an ID field when I create the assessments //Create list of account IDs for object list accs - is this necessary or could I reference the object list directly? //List<ID> refAccs = new List<ID> (); //for (Account z:accs){ //refAccs.add(z.Id); //} //Query client forms for account IDs and put in a map //Map<ID,Client_Form__c> IEFs = new Map<ID,Client_Form__c>([SELECT Id, Account__c FROM Client_Form__c WHERE Account__c IN :refAccs]); //For each account, create two IEF assessments for(Account a:accs){ Assessment__c assess = new Assessment__c(); assess.Client_Form__c = IEFs.get(a.Id); assess.RecordTypeId = '01280000000BR0VAAW'; assess.OwnerId = ownerId; assess.Account__c = a.Id; assessments.add(assess); assessments.add(assess.clone()); } //Insert the assessments insert assessments; } }

Thanks in advance,

 

Joe

 

 

 

Best Answer chosen by Admin (Salesforce Developers) 
ShikibuShikibu

Try this. It creates a set from the list of accounts, then queries all the forms that it will need for all of those accounts, then populates the map.

 

 

Map<Id,Id> accountFormMap = new Map<Id,Id>(); for (Account a: accs) { accountFormMap.put(a.Id, null); } for (Client_Form__c form : [SELECT ID, Account__c FROM Client_Form__c WHERE Account__c in :accountFormMap.keySet()]) { accountFormMap.put(Account__c, Id); }

 

 I find that I frequently need a set of ids from a list of SObjects, so I have created a Utility class, and it contains this method:

 

 

// given a list of SObjects, return a set of ids public static Set<Id> setOfIdFromListOfSObject ( List<SObject> oList ) { Set<Id> Ids = new Set<Id> {}; for (SObject o : oList) { Ids.add( o.id ); } return Ids; }

 

 Using this utility method, the solution can be expressed more compactly:

 

 

Map<Id,Id> accountFormMap = new Map<Id,Id>(); for (Client_Form__c form : [SELECT ID, Account__c FROM Client_Form__c WHERE Account__c in :Utility.setOfIdFromListOfSObject(accs)]) { accountFormMap.put(Account__c, Id); }

 

 

 

 

 

 

 

All Answers

ShikibuShikibu

Try this. It creates a set from the list of accounts, then queries all the forms that it will need for all of those accounts, then populates the map.

 

 

Map<Id,Id> accountFormMap = new Map<Id,Id>(); for (Account a: accs) { accountFormMap.put(a.Id, null); } for (Client_Form__c form : [SELECT ID, Account__c FROM Client_Form__c WHERE Account__c in :accountFormMap.keySet()]) { accountFormMap.put(Account__c, Id); }

 

 I find that I frequently need a set of ids from a list of SObjects, so I have created a Utility class, and it contains this method:

 

 

// given a list of SObjects, return a set of ids public static Set<Id> setOfIdFromListOfSObject ( List<SObject> oList ) { Set<Id> Ids = new Set<Id> {}; for (SObject o : oList) { Ids.add( o.id ); } return Ids; }

 

 Using this utility method, the solution can be expressed more compactly:

 

 

Map<Id,Id> accountFormMap = new Map<Id,Id>(); for (Client_Form__c form : [SELECT ID, Account__c FROM Client_Form__c WHERE Account__c in :Utility.setOfIdFromListOfSObject(accs)]) { accountFormMap.put(Account__c, Id); }

 

 

 

 

 

 

 

This was selected as the best answer
paddingtonpaddington

Thanks Shikibu. That worked perfectly and I love the Utility class solution. I've been meaning to sort that out for a while, particularly after reading these posts [1, 2] by Steve Andersen at the Salesforce Foundation. For completeness, the final code looks like this:

 

TRIGGER:

 

trigger IEFawaitingAssessment on Account (after update) { //Create list of accounts for which the IEF Sub-stage has become awaiting two assessments List<Account> ChangedAccounts = new List<Account> (); for(Integer i=0; i<Trigger.new.size(); i++){ if(Trigger.new[i].RecordTypeId == '01280000000BQgyAAG' && Trigger.old[i].Initial_Enquiry_Sub_stage__c != 'Awaiting two assessments' && Trigger.new[i].Initial_Enquiry_Sub_stage__c == 'Awaiting two assessments'){ ChangedAccounts.add(Trigger.new[i]); } } //Pass this list to the createAssessments class Assessments.createIEFfromAccount(ChangedAccounts); }

 METHOD:

 

public static void createIEFfromAccount(List<Account> accs){ //Declare owner ID variable for IEF Assessments queue Id ownerId = [Select q.Queue.Id from QueueSobject q where q.Queue.Name = 'IEF Assessments' and q.SobjectType = 'Assessment__c'].Queue.Id; //Create a list of assessments to be inserted List<Assessment__c> assessments = new List<Assessment__c> (); //Call setOfIdFromListOfSObject Utility method to populate a map of account ID to client form ID Map<Id,Id> IEFmap = new Map<Id,Id>(); for (Client_Form__c form : [SELECT ID, Account__c FROM Client_Form__c WHERE RecordTypeId = '01280000000BQlBAAW' AND Account__c in :Utility.setOfIdFromListOfSObject(accs)]) { IEFmap.put(form.Account__c, form.Id); } //For each account, create two IEF assessments for(Account a:accs){ Assessment__c assess = new Assessment__c(); assess.Client_Form__c = IEFmap.get(a.Id); assess.RecordTypeId = '01280000000BR0VAAW'; assess.OwnerId = ownerId; assess.Account__c = a.Id; assessments.add(assess); assessments.add(assess.clone()); } //Insert the assessments insert assessments; }

 

 

 UTILITY METHOD:

 

//Given a list of SObjects, return a set of ids public static Set<Id> setOfIdFromListOfSObject ( List<SObject> oList ) { Set<Id> Ids = new Set<Id> {}; for (SObject o : oList) { Ids.add( o.id ); } return Ids; }

 

 

 

 

JerryHJerryH

So there's no direct way of retrieving a list of ID's using SOQL without having to do a loop / add?  Why doesn't something as obvious as "List<Id> lstID = new List<Id>([select Id from Account where (some condition)])" work?

ShikibuShikibu

Because apex is derived from Java, not from the more expressive language python!