Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

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

share|improve this question

3 Answers 3

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) ...
share|improve this answer
    
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 Feb 3 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 Feb 3 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 Feb 3 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 Feb 3 at 19:56

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);
}
share|improve this answer
    
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 Feb 3 at 16:50
    
That's still not what the OP is looking for, precisely. –  sfdcfox Feb 3 at 16:52
    
Actually, no it won't - you should query all contracts, loop and group them. Let me re-edit... –  dino_zg Feb 3 at 16:52

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);
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.