4

Here's the scenario: you've loaded a bunch of account IDs as keys into your map. But now, instead of loading account records as the sObject values, you're going to load a related object, such as Contracts. Here's my code to illustrate:

map<id, contract> accIDtoContract = new map<id, contract>();

    for( Usage_Data__c UD : Usage_Data_List ){
            accIDtoContract.put(UD.Account__c, NULL);                      
    }

    accIDtoContract.remove(null); 

    accIDtoContract.putAll([SELECT ID, random_field
                                   FROM Contract WHERE AccountID IN :accIDtoContract.keySet()]);

First off, am I correct in assuming that the contract records will populate by matching their Account ID lookup field to the keys in the map?

Next, how does PUTALL handle it when you try to add mutiple values that relate to the same key (per my example, when the SOQL query in the PUTALL call returns more than one contract per account ID)?

Thanks for your help guys

3 Answers 3

8

PutAll only works on the ID field of an SObject. It is syntatic sugar for:

for(SObject record: someList)
    someMap.put(record.Id, record);

To use any other type of mapping, you would have to make your own for loop.

Such a for loop would look like:

Map<Id, Contract[0]> someMap = new Map<id, contract[0]>();

... load account ids here ...

for(Contract record: [SELECT Id, AccountId, random_field FROM Contract WHERE AccountId IN :someMap.keyset()]) {
    if(someMap.get(record.AccountId) == null) {
        someMap.put(record.AccountId, new Contract[0]);
    }
    someMap.get(record.AccountId).add(record);
}

In your specific case, I might consider using a subquery:

someMap.putAll([SELECT Id, (SELECT Id, random_field FROM Contracts) FROM Account WHERE Id IN :someMap.keySet()]);

You can access a list of contracts via:

for(Contract record: someMap.get(accountId).Contracts) ...
4
  • Thanks sfdcfox, I think I'll use that subquery you suggest. My goal is to be able to tie just a few contracts to a related object via their mutual relationship to an account. Using the PUTALL method was my way to minimize the # of SOQL queries. Now, as my prog goes through each record of the related object, it can loop through the few contracts tied to the related account to search for the necessary info I want pulled into the object.
    – smohyee
    Commented Feb 3, 2014 at 16:57
  • 1
    @smohyee FYI, the "sub-query" method counts as two queries, even though it's only one physical query in the code. If you're looking to minimize the query counter, use the first loop I suggested, while if you're looking to reduce CPU time spent, you'd want to use the second form. Each has a trade-off.
    – sfdcfox
    Commented Feb 3, 2014 at 17:11
  • @smohyee And if you're really desperate, consider a third alternative: Store the accounts in a map, then loop over the contracts you query and update the accounts in the map, like: map<id, account> acc = new map<id, account>(); /* load accounts */ for(contract record: [select id, accountid, random_field from contract where accountid in :acc.keyset()]) acc.get(record.accountid).other_field += record.random_field;
    – sfdcfox
    Commented Feb 3, 2014 at 17:13
  • 1
    The sub-query is the way to go. @smohyee: From what you're saying on the first comment to this answer, it looks like you want two sub-queries instead of the one that is specified in sfdcfox's answer. You can do so like this: [select id, (select id, random_field from contracts), (select id, random_field from some_other_account_child_objects), from account where Id ...] I believe there is a hard limit of 20 sub-queries in any one query.
    – willard
    Commented Feb 3, 2014 at 19:56
2

So, basically, you would like to have a map of key-value pairs, where key is the AccountID, and the value is list of Contracts related to the Account? If so, first of all, rather than having Map < id, contract >, what you are really looking is Map < id, List < Contract > >.

Further on, you could store the list of id's - in the list. And only then use the list of ids to query the contracts and populate the map:

map<id, contract> accIDtoContract = new map<id, List<contract>>();

List<id> accIDs = new List<id>();
for( Usage_Data__c UD : Usage_Data_List ){
    // Don't store AccountID in the list if it was already stored
    if(accIDs.contains(UD.Account__c))
        continue;

    accIDs.add(UD.Account__c);                      
    accIDtoContract(UD.Account__c, new List<Contract>());
}

List<contract> contracts = [SELECT ID, AccountID, random_field FROM Contract WHERE AccountID IN :accIDs]);

for(Contract c : contracts) {
    accIDtoContract.get(c.AccountID).add(c);
}
3
  • Thanks for that, makes sense. Now what about mapping the contracts using the their accountID related field? Will the PUTALL method recognize that I'm trying to match contract values to keys based on the related accountID value, or do I need to specify that somehow?
    – smohyee
    Commented Feb 3, 2014 at 16:50
  • That's still not what the OP is looking for, precisely.
    – sfdcfox
    Commented Feb 3, 2014 at 16:52
  • Actually, no it won't - you should query all contracts, loop and group them. Let me re-edit...
    – dino ursic
    Commented Feb 3, 2014 at 16:52
1

Mayby something like this:

map<id, contract[]> accIDtoContract = new map<id, contract[]>();
Set<Id> accIds = new set<Id>();
for( Usage_Data__c UD : Usage_Data_List ){
            accIds.add(UD.Account__c);                      
}

if(accIds.isEmpty()){
    return;
}

for(Account acc : [SELECT Id, /** Other Fields **/ (SELECT Id /** Other Fields **/ FROM Contracts) FROM Account WHERE Id IN :accIds){
    accIDtoContract.put(acc.Id, acc.Contracts);
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .