Find Accounts by Contact
Query the org (SOQL) · Intermediate · 15 min · Runs in your Salesforce Developer org · Apex Path
Scenario. A campaign selector needs Accounts that have a qualifying Contact, and a follow-up list of Accounts that don't, without loading every Contact.
Use IN and NOT IN with a Contact subquery to find tagged Accounts that do and don't have a matching Contact.
What you will learn
A semi-join keeps parent records that have a matching child. The subquery returns one lookup field, such as AccountId.
SELECT Id FROM Account
WHERE Id IN (SELECT AccountId FROM Contact WHERE Email = :wanted)
NOT IN flips it (an anti-join): Accounts with no matching Contact.
Rules. The subquery selects one Id or lookup field from another object. It can't use ORDER BY or LIMIT or be joined with OR, and one WHERE can hold two at most.
The task
Return the tagged Account that has a Contact with the wanted Email, then use NOT IN to return the tagged Accounts that don't.
Examples:
- only Qualified has a Contact with the wanted Email →
Semi count: 1andSemi matched: true - Other (different Email) and Empty (no Contacts) → left out by IN, returned by NOT IN →
Anti count: 2 - the orphan Contact has the Email but no Account → adds no Account
Requirements:
- Keep both outer queries to this run with
Name LIKE :tagPrefix. - Filter Account Id with
INand a Contact subquery that selectsAccountIdformatchingEmail. - Run the same filter with
NOT INin a second query. - Print the IN count, the three IN checks, and the NOT IN count. Print
Semi count:,Semi matched:,Semi nonmatch excluded:,Semi empty excluded:andAnti count:.
Starter code
String tag = 'AS-Q309-' + UserInfo.getUserId() + '-' +
String.valueOf(Datetime.now().getTime());
String tagPrefix = tag + '%';
List<Account> parents = new List<Account>{
new Account(Name = tag + '-Qualified'),
new Account(Name = tag + '-Other'),
new Account(Name = tag + '-Empty')
};
insert parents;
String emailToken = 'q' + String.valueOf(Datetime.now().getTime());
String matchingEmail = emailToken + '@example.com';
insert new List<Contact>{
new Contact(LastName = tag + '-Match', AccountId = parents[0].Id, Email = matchingEmail),
new Contact(LastName = tag + '-Other', AccountId = parents[1].Id,
Email = emailToken + '@other.example.com'),
new Contact(LastName = tag + '-Orphan', Email = matchingEmail)
};
List<Account> matches = new List<Account>();
List<Account> nonMatches = new List<Account>();
System.debug('Semi count: ' + matches.size());
System.debug('Semi matched: false');
System.debug('Semi nonmatch excluded: false');
System.debug('Semi empty excluded: false');
System.debug('Anti count: ' + nonMatches.size());
When it passes
IN returns only Qualified. NOT IN returns Other and Empty. The orphan Contact adds no Account to either list.
You write the Apex yourself. ApexSensei runs it and tells you what passed and what did not.