Build Contacts with one query and one insert
Ship it safely · Intermediate · 18 min · Runs in your Salesforce Developer org · Apex Path
Scenario. A service links new Contacts to parent Accounts. It works for 5 rows, but a 200-row batch would hit the 100-query limit.
Refactor a loop that queries and inserts once per record into one query, a Map lookup and one insert.
What you will learn
A query inside a loop costs one SOQL query per record, and a transaction allows only 100. The fix: run one query with IN, put the rows in a Map, then look each one up inside the loop.
Map<String, Id> idByName = new Map<String, Id>();
for (Account row : [SELECT Id, Name FROM Account WHERE Name IN :names]) {
idByName.put(row.Name, row.Id);
}
Gotcha. DML inside a loop has the same problem. Build the list first, then insert it once.
The task
Rewrite the slow loop so it finds every parent Account with one query and a Map, then inserts all Contacts at once.
Examples:
- the starter queries and inserts once per Contact →
Queries used: 5andDML used: 5 - one query into a Map, then one insert after the loop →
Queries used: 1andDML used: 1 - every new Contact points at one of the new Accounts →
Contacts linked: 5
Requirements:
- Before the loop, load the parent Accounts with one query that uses
IN. - Put them in a Map from Account Name to Id, and look each parent up there inside the loop.
- In the loop, only build Contacts. Insert the list once, after the loop.
- Keep measuring from
queriesBeforeanddmlBefore, and printQueries used:andDML used:. - Count the saved Contacts whose AccountId is one of
parents, and printContacts linked:.
Starter code
String tag = 'AS-SHIP-' + UserInfo.getUserId() + '-' +
String.valueOf(Datetime.now().getTime());
List<Account> parents = new List<Account>{
new Account(Name = tag + '-North'),
new Account(Name = tag + '-South')
};
insert parents;
List<String> parentNames = new List<String>{
tag + '-North', tag + '-South', tag + '-North', tag + '-South', tag + '-North'
};
Integer queriesBefore = Limits.getQueries();
Integer dmlBefore = Limits.getDmlStatements();
for (String parentName : parentNames) {
Account parent = [SELECT Id FROM Account WHERE Name = :parentName LIMIT 1];
insert new Contact(LastName = tag + '-Contact', AccountId = parent.Id);
}
System.debug('Queries used: ' + (Limits.getQueries() - queriesBefore));
System.debug('DML used: ' + (Limits.getDmlStatements() - dmlBefore));
When it passes
One query fills a Map of parent Ids, the loop only builds Contacts, one insert saves them, and all 5 Contacts link to the new Accounts. The grader rolls everything back.
You write the Apex yourself. ApexSensei runs it and tells you what passed and what did not.