This guide walks through 68 aggregate queries, subqueries, multi-level relationship traversal, and SOSL search challenges. Each entry below includes the full problem statement, the governor-limit and best-practice constraints it's testing, and a numbered approach to solving it — then you can open the same problem in ApexArena's browser-based Apex editor and get instant pass/fail feedback against real test cases.
Hard
SOQL
1. Nth Highest Salary Finder
Problem #3 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL query to find the Nth highest Salary__c
from a custom object Employee__c. Return the full employee record at that salary rank.
Handle ties by counting distinct salary values.
Example
Salaries: [90, 85, 85, 70, 60]
N = 2 → employees with salary 85
N = 3 → employees with salary 70
Approach
- 1The aggregate query returns N distinct salaries in DESC order — the last element is the Nth highest.
- 2Then query: SELECT Id, Name, Salary__c FROM Employee__c WHERE Salary__c = :nthSalary
- 3Edge case: if fewer than N distinct salaries exist, return an empty list.
Hard
SOQLGovernor
2. Dynamic SOQL Builder
Problem #20 · Salesforce Apex Coding Challenge
Problem Statement
Build a reusable utility method that constructs and executes a
dynamic SOQL query from parameters, following all Salesforce security
best practices.
Method Signature
public static List<SObject> buildAndRun(
String objectType,
List<String> fields,
String whereClause,
Integer limitCount
)
Security Requirements
- Use
String.escapeSingleQuotes() on the where clause to prevent SOQL injection
- Always include a
LIMIT clause (default to 200 if null)
- Use
String.join() to concatenate field names
- Call
Database.query() to execute
Approach
- 1Build the query string: 'SELECT ' + String.join(fields, ', ') + ' FROM ' + objectType
- 2Always escape: WHERE ' + String.escapeSingleQuotes(whereClause)
- 3Append LIMIT: ' LIMIT ' + (limitCount != null ? limitCount : 200)
- 4Execute with: return Database.query(query);
Medium
SOQL
3. Parent-to-Child Relationship Query
Problem #82 · Salesforce Apex Coding Challenge
Problem Statement
Write a static method that returns all Account records along with their
related Contact records in a single SOQL query using a
child relationship subquery in the SELECT clause.
Method Signature
public static List<Account> getAccountsWithContacts()
Key Concept — Child Subquery
A child relationship subquery sits inside the outer SELECT clause in parentheses.
You use the relationship name (the plural form of the child object, for a
standard-object relationship) rather than the object name itself.
SELECT Id, Name, (SELECT Id, SomeField FROM ChildRelationshipName)
FROM ParentObject
Constraints
- Use a single SOQL query — no loops, no separate Contact query
- Include at least
Id and LastName in the subquery SELECT
- Order results by
Account.Name ASC
- No
ORDER BY or LIMIT inside the child subquery (not allowed)
Approach
- 1The child relationship name for Contact under Account is Contacts (plural, no __r suffix for standard objects).
- 2A child subquery goes inside the outer SELECT clause, wrapped in parentheses, and uses the relationship name instead of the object name.
- 3Access child records in Apex: for (Contact c : acc.Contacts) { ... }
Medium
SOQL
4. Semi-Join: Contacts at Accounts with Won Deals
Problem #83 · Salesforce Apex Coding Challenge
Problem Statement
Write a method that returns all Contact records whose parent
Account has at least one Closed Won
Opportunity — using a SOQL semi-join (IN with a subquery).
Method Signature
public static List<Contact> getContactsAtWinningAccounts()
Key Concept — Semi-Join
A semi-join filters the outer query by checking whether a relationship field
appears in the result of a subquery:
WHERE SomeField IN (SELECT ParentField FROM ChildObject WHERE ...)
The subquery must select a single field that matches the type
of the outer WHERE field.
Constraints
- Use a single SOQL statement — no Apex loops to compare records
- The subquery must query
Opportunity and filter on StageName
- Return at least
Id, FirstName, LastName, AccountId
Approach
- 1A semi-join filters the outer query with WHERE field IN (subquery) — the subquery runs against the related object and returns the matching parent Ids.
- 2The subquery field must be an Id or relationship field — it must match the type of the outer WHERE field.
- 3You cannot use ORDER BY or LIMIT inside a semi-join subquery.
Medium
SOQL
5. Anti-Join: Accounts with No Open Cases
Problem #84 · Salesforce Apex Coding Challenge
Problem Statement
Write a method that returns all Account records that have
no open Cases — using a SOQL anti-join
(NOT IN with a subquery).
Method Signature
public static List<Account> getAccountsWithNoOpenCases()
Key Concept — Anti-Join
An anti-join excludes records whose Id appears in a subquery result:
WHERE Id NOT IN (SELECT ParentField FROM ChildObject WHERE ...)
Constraints
- Use NOT IN with a subquery — not a loop-based approach
- Subquery must filter for open (non-Closed) Cases
- Return at least
Id and Name
- Order results by
Name ASC
Approach
- 1An anti-join excludes records with WHERE field NOT IN (subquery) — the subquery returns the Ids that SHOULD be excluded from the outer result.
- 2The subquery field must be an Id-type field that matches the outer WHERE field (Id).
- 3A lookup field can be null on some child records — to be safe, also filter those out inside the subquery.
Hard
SOQLGovernor
6. GROUP BY + HAVING: Accounts with More than 2 Contacts
Problem #85 · Salesforce Apex Coding Challenge
Problem Statement
Write a method that uses a SOQL aggregate query to find all
Account IDs that have more than 2 related Contacts.
Method Signature
public static List<AggregateResult> getHighContactAccounts()
Key Concept — HAVING
HAVING filters after aggregation — it is the equivalent of
WHERE for aggregate results. You cannot use WHERE to filter
on a COUNT() result.
SELECT GroupField, COUNT(Id) alias
FROM ChildObject
GROUP BY GroupField
HAVING COUNT(Id) > someThreshold
Constraints
- Use
GROUP BY AccountId — do not loop over contacts in Apex
- Use
HAVING COUNT(Id) > 2 to filter at the database level
- Order by
COUNT(Id) DESC to show most-contacted accounts first
- Access results with:
(Integer) result.get('cnt')
Approach
- 1HAVING filters after GROUP BY — use it to filter on aggregate values like COUNT(Id).
- 2Alias the count for easy access, e.g. COUNT(Id) cnt — then use result.get('cnt') in Apex.
- 3The HAVING expression must reference the same aggregate function used in SELECT.
Medium
SOQLAggregate
7. Total Opportunities Per Account
Problem #108 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL aggregate query to find the total number of Opportunities
for each Account, returning the highest-count accounts first.
Requirements
- SELECT
AccountId, COUNT(Id) oppCount
- FROM
Opportunity
- GROUP BY
AccountId
- ORDER BY
COUNT(Id) DESC
- Return type:
List<AggregateResult>
Access Results
for (AggregateResult ar : results) {
Id accId = (Id) ar.get('AccountId');
Integer cnt = (Integer) ar.get('oppCount');
}
Approach
- 1COUNT(Id) oppCount gives each group a named alias.
- 2GROUP BY AccountId splits results into one row per account.
- 3ORDER BY COUNT(Id) DESC puts the busiest accounts first.
Hard
SOQLSubquery
8. Contacts Associated with a Specific Opportunity
Problem #109 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve the names and emails of all Contacts whose Account
has an Opportunity matching a given opportunity name.
Use a semi-join to find relevant AccountIds from the Opportunity
object, then return Contacts for those accounts.
Requirements
- Return each matching Contact's
Id, Name, and Email.
- Use a semi-join against Opportunity to find the qualifying Accounts — no Apex loops.
- Method parameter:
String opportunityName
Approach
- 1Contacts are not directly linked to Opportunities — use Account as the bridge.
- 2A semi-join (WHERE field IN (subquery)) lets you filter Contacts by a condition on their related Opportunity without an Apex loop.
- 3Bind the parameter with :opportunityName inside the subquery.
Medium
SOQLSubquery
9. Accounts with Opportunities Greater Than $50,000
Problem #112 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve Account names that have at least one associated Opportunity
with an Amount greater than $50,000.
Use a semi-join to avoid duplicate Account rows.
Requirements
- Return each qualifying Account's
Id, Name, and Industry.
- Use a semi-join against Opportunity to find Accounts with a large enough deal — no Apex loops.
- Sort the result alphabetically by Account Name.
Approach
- 1A semi-join (WHERE field IN (subquery)) lets you filter Accounts by a condition on their related Opportunities without an Apex loop.
- 2Outer object is Account; inner object is Opportunity.
- 3Amount values in SOQL are numeric — no $ symbol.
Medium
SOQLSubquery
10. Contacts on Opportunities in the Proposal Stage
Problem #115 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve the names and email addresses of all Contacts
whose Account has at least one Opportunity in the
"Proposal/Price Quote" stage.
Approach
Use a semi-join: filter Contacts by AccountIds that have a Proposal-stage Opportunity.
Requirements
- Return each qualifying Contact's
Id, Name, and Email.
- Use a semi-join against Opportunity to find the qualifying Accounts — no Apex loops.
Approach
- 1Contact is not directly linked to Opportunity — use Account as the bridge.
- 2A semi-join on AccountId, filtered to Accounts with a matching Opportunity stage, avoids an Apex loop.
- 3Full stage name is 'Proposal/Price Quote' — include the slash.
Hard
SOQLAggregate
11. Account with the Maximum Number of Opportunities
Problem #116 · Salesforce Apex Coding Challenge
Problem Statement
Find and return the AccountId of the Account that has
the highest number of associated Opportunities.
Requirements
- SELECT
AccountId, COUNT(Id) cnt
- GROUP BY
AccountId → ORDER BY COUNT(Id) DESC → LIMIT 1
- Return the AccountId from the first (and only) result row
- Return
null if no Opportunities exist
Approach
- 1GROUP BY AccountId + ORDER BY COUNT(Id) DESC + LIMIT 1 gives the single top account.
- 2Access with: (Id) results[0].get('AccountId').
- 3Return null if results is empty.
Hard
SOQLSubquery
12. Accounts That Have Both Opportunities and Cases
Problem #118 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve all Accounts that have both at least one Opportunity
and at least one Case, using two semi-joins combined with AND.
Requirements
- Include only Accounts that have at least one related Opportunity.
- Also require at least one related Case on the same Account.
- Single SOQL statement — no Apex loops.
Approach
- 1Chain two semi-joins together with AND — one per related object.
- 2First semi-join filters by Opportunity ownership.
- 3Second semi-join filters by Case ownership.
Medium
SOQLDate Literals
13. Opportunities Not Updated in the Last 30 Days
Problem #119 · Salesforce Apex Coding Challenge
Problem Statement
Identify Opportunities that have not been modified in the last 30 days.
Key Concept
LastModifiedDate < LAST_N_DAYS:30 means the record was modified
more than 30 days ago — i.e., it sits outside (before) the rolling window.
Requirements
- WHERE
LastModifiedDate < LAST_N_DAYS:30
- SELECT
Id, Name, StageName, LastModifiedDate
- ORDER BY
LastModifiedDate ASC (oldest first)
Approach
- 1LAST_N_DAYS:30 is a rolling 30-day window ending now.
- 2Use < to find records whose last modification is BEFORE (older than) that window.
- 3ORDER BY LastModifiedDate ASC surfaces the most stale records first.
Hard
SOQLRelationship Query
14. Contacts Whose Mailing State Differs from Their Account's Billing State
Problem #122 · Salesforce Apex Coding Challenge
Problem Statement
Find Contacts whose MailingState is not equal to
their parent Account's BillingState — these Contacts are located in a
different state than their company's billing address.
Key Concept — SOQL Cannot Compare Two Fields Directly
Unlike standard SQL, a SOQL WHERE clause can only compare a field against a
literal, bind variable, or subquery — never against another field
(WHERE MailingState != Account.BillingState is not valid SOQL
and will not compile). The correct pattern is to query the candidate records with only
null guards in the WHERE clause, then perform the actual field-to-field
comparison in Apex.
Requirements
- Query Contacts selecting
Id, Name, MailingState and the related Account.BillingState
- Guard the query against nulls on both
MailingState and Account.BillingState so blank addresses don't produce false mismatches
- Loop over the query results in Apex and compare
c.MailingState != c.Account.BillingState,
collecting only the mismatches to return
Approach
- 1Traverse the Account lookup with dot-notation: Account.BillingState.
- 2SOQL's WHERE clause cannot compare two fields directly — WHERE MailingState != Account.BillingState is invalid syntax.
- 3Query with only null guards in WHERE, then compare c.MailingState != c.Account.BillingState inside an Apex loop.
Medium
SOQLAggregate
15. Account Names with Total Amount of All Associated Opportunities
Problem #124 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve each Account's total Opportunity Amount —
the sum of all Opportunity Amounts grouped by Account, highest total first.
Requirements
- SELECT
AccountId, SUM(Amount) totalAmount
- FROM
Opportunity
- GROUP BY
AccountId
- ORDER BY
SUM(Amount) DESC
Access
Decimal total = (Decimal) ar.get('totalAmount');
Approach
- 1SUM(Amount) totalAmount sums all opportunity amounts per account group.
- 2GROUP BY AccountId creates one result row per account.
- 3ORDER BY SUM(Amount) DESC puts the account with the most pipeline value first.
Hard
SOQLAggregateCollections
16. Contacts Related to Opportunities with Above-Average Amount
Problem #126 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve the names and emails of all Contacts whose Account has at least one
Opportunity with an Amount greater than the average Opportunity Amount.
3-Step Approach
- Compute the average Opportunity Amount using an aggregate query.
- Collect the AccountIds of Opportunities whose Amount exceeds that average.
- Fetch the Name and Email of Contacts belonging to those Accounts.
Governor Rules
- No SOQL inside loops — collect IDs first, then query
- Maximum 3 SOQL queries total
Approach
- 1Step 1 — use an AVG aggregate over Opportunity Amount.
- 2Step 2 — bind the computed average into a WHERE Amount > :avgAmt filter.
- 3Step 3 — collect matching AccountIds into a Set, then query Contact filtered by that set.
- 4Never put SOQL inside a loop — collect all IDs first.
Hard
SOQLCollections
17. Top 3 Opportunities with the Highest Amount for Each Account
Problem #127 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve the top 3 Opportunities by Amount for each Account,
returned as a Map<Id, List<Opportunity>> keyed by AccountId.
Why Apex?
SOQL does not support LIMIT n PER GROUP, so post-query Apex logic
is needed to cap each account's list at 3.
Requirements
- One SOQL query: ORDER BY
Amount DESC
- Iterate and stop adding once a group reaches 3 entries
- Return
Map<Id, List<Opportunity>>
Approach
- 1ORDER BY Amount DESC means the first opportunity you encounter per account is the highest.
- 2containsKey(opp.AccountId) checks whether the account already has a list entry.
- 3.size() < 3 before adding ensures each account never exceeds 3 records.
Medium
SOQLCustom Fields
18. Opportunities with Approval Status Set to "Pending"
Problem #128 · Salesforce Apex Coding Challenge
Problem Statement
Identify Opportunities where the custom field Approval_Status__c
is set to "Pending".
Custom Field Convention
All custom fields in Salesforce end with __c. Reference them
exactly as defined — casing and underscores matter.
Requirements
- Filter Opportunities to those whose
Approval_Status__c equals "Pending"
- Include
Approval_Status__c in the selected fields
- Sort the results by
Amount, highest first
Approach
- 1Custom fields always use the __c suffix: Approval_Status__c.
- 2Filter on the Approval_Status__c field equaling the exact text "Pending".
- 3Include Approval_Status__c among the selected fields so callers can verify the value.
Medium
SOQLDate Literals
19. Accounts Not Modified in the Last 60 Days
Problem #129 · Salesforce Apex Coding Challenge
Problem Statement
Find all Accounts that have not been modified in the last 60 days.
These are candidates for re-engagement or data cleanup.
Key Concept
LastModifiedDate < LAST_N_DAYS:60 returns records whose last
modification is before the 60-day rolling window (older than 60 days).
Requirements
- WHERE
LastModifiedDate < LAST_N_DAYS:60
- ORDER BY
LastModifiedDate ASC (oldest first)
Approach
- 1LAST_N_DAYS:60 is a rolling window. Use < to find records OLDER than that window.
- 2Contrast: > finds records modified WITHIN the last 60 days.
- 3ORDER BY LastModifiedDate ASC surfaces the most stale accounts at the top.
Medium
SOQLDate Literals
20. Closed Won Opportunities with a Close Date in the Last Quarter
Problem #130 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve all Closed Won Opportunities whose
CloseDate falls within the last calendar quarter.
Key Concept
LAST_QUARTER covers the complete previous fiscal quarter
(Q1, Q2, Q3, or Q4) relative to today.
Requirements
- WHERE
StageName = 'Closed Won' AND CloseDate = LAST_QUARTER
- ORDER BY
Amount DESC
Approach
- 1LAST_QUARTER covers the entire previous calendar quarter automatically.
- 2Combine: StageName = 'Closed Won' AND CloseDate = LAST_QUARTER.
- 3No date arithmetic needed — LAST_QUARTER is a built-in date literal.
Medium
SOQLAggregate
21. Account Names with the Total Number of Associated Contacts
Problem #131 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL aggregate query that returns each Account's
total Contact count, ordered from most contacts to fewest.
Requirements
- SELECT
AccountId, COUNT(Id) contactCount
- FROM
Contact — not Account
- GROUP BY
AccountId
- ORDER BY
COUNT(Id) DESC
Approach
- 1Query FROM Contact — the Contact holds the AccountId foreign key.
- 2COUNT(Id) contactCount counts how many contacts each account has.
- 3ORDER BY COUNT(Id) DESC ranks accounts by their contact volume.
Medium
SOQLCustom Fields
22. High-Priority Opportunities with Amount Greater Than $50,000
Problem #133 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve Opportunities where the custom field Priority__c
is "High" and the Amount exceeds $50,000.
Requirements
- Filter to Opportunities where
Priority__c is "High" and Amount exceeds 50,000
- Include
Priority__c among the selected fields
- Sort the results by
Amount, highest first
Approach
- 1Custom fields use __c suffix: Priority__c.
- 2Combine both filters: Priority__c must equal 'High' and Amount must exceed 50000.
- 3Numeric SOQL comparisons use no $ symbol — just the raw number.
Medium
SOQLSubquery
23. Accounts That Have at Least One Prospecting Opportunity
Problem #134 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve the names of all Accounts that have at least one Opportunity
in the "Prospecting" stage, using a semi-join to avoid duplicate rows.
Requirements
- Return each qualifying Account's
Id, Name, and Industry.
- Use a semi-join against Opportunity, filtered to the "Prospecting" stage.
- Sort the result alphabetically by Name.
Approach
- 1A semi-join on AccountId, filtered to Opportunity's 'Prospecting' stage, finds the qualifying Accounts.
- 2Returns unique Account rows even if an Account has multiple Prospecting Opps.
- 3Outer query is FROM Account; inner subquery is FROM Opportunity.
Medium
SOQLChild-to-Parent
24. SOQL Child-to-Parent: Opportunity with Account Owner (2-Level)
Problem #188 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL query that traverses two levels of parent relationships: Opportunity → Account → Owner.
Requirements
- Return each Opportunity's
Id, Name, StageName, and Amount, plus its parent Account's Name and the Account Owner's Name.
- Only include Opportunities that are still open.
- Sort by the parent Account's Name, and cap the result at 20 rows.
Key Concept
SOQL supports up to 5 levels of parent traversal using chained dot notation: Account.Owner.Name goes Opportunity → Account → User (Owner).
Approach
- 1Chain dot notation two levels deep to reach the Account's Owner (Opportunity → Account → User)
- 2SOQL allows up to 5 levels of parent traversal
- 3Filter to Opportunities that are not yet closed
Medium
SOQLChild-to-Parent
25. SOQL Child-to-Parent: Filter Contacts by Account Type
Problem #189 · Salesforce Apex Coding Challenge
Problem Statement
Write a child-to-parent query that filters Contacts by their parent Account's Type field.
Requirements
- Return each Contact's
Id, FirstName, LastName, Email, and Phone, plus their parent Account's Name and Type.
- Only include Contacts whose Account's Type is "Customer".
- Sort by LastName, and cap the result at 50 rows.
Approach
- 1Filter on the parent Account's Type field using dot notation in the WHERE clause
- 2Include the parent Account's Type field in your SELECT to display it in results
- 3Account.Type common values: Customer, Partner, Prospect, Other
Medium
SOQLChild-to-Parent
26. SOQL Child-to-Parent: Case with Contact and Account Names
Problem #190 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL query that retrieves Cases with fields from two different parent objects: Contact and Account.
Requirements
- Return each Case's
Id, CaseNumber, Subject, and Status, plus its Contact's first/last name and email, and its Account's name and phone.
- Only include Cases that are not yet closed.
- Sort by CaseNumber, and cap the result at 20 rows.
Approach
- 1Case has two parent lookups: ContactId (to Contact) and AccountId (to Account)
- 2Reach each parent's fields with dot notation off its own relationship name
- 3Both parent traversals belong in the same query
Medium
SOQLChild-to-Parent
27. SOQL Child-to-Parent: Filter Opportunities by Account Revenue
Problem #191 · Salesforce Apex Coding Challenge
Problem Statement
Write a query that returns open Opportunities where the parent Account's AnnualRevenue meets a minimum threshold.
Requirements
- Return each Opportunity's
Id, Name, StageName, Amount, and CloseDate, plus its parent Account's Name and AnnualRevenue.
- Only include still-open Opportunities whose parent Account's AnnualRevenue meets a $1,000,000 minimum threshold.
- Sort by the parent Account's AnnualRevenue, largest first, and cap the result at 20 rows.
Approach
- 1Filter on the parent Account's numeric AnnualRevenue field using dot notation
- 2Combine the revenue threshold and the open-status filter with AND
- 3Sort descending by the parent Account's AnnualRevenue to see largest accounts first
Medium
SOQLChild-to-Parent
28. SOQL Child-to-Parent: Contact Directory with Multiple Account Fields
Problem #192 · Salesforce Apex Coding Challenge
Problem Statement
Build a contact directory query that retrieves multiple Account fields alongside contact details for a richer data set.
Requirements
- Return each Contact's
Id, FirstName, LastName, Email, Phone, and Title, along with the parent Account's Name, Type, BillingCity, and Website.
- Only include contacts whose parent Account has a Name populated.
- Sort primarily by the Account's Name, then by the contact's LastName, and cap the results at 50 rows.
Approach
- 1You can select several fields from the same parent relationship in one query, e.g. Account.Name alongside other Account fields.
- 2ORDER BY can reference multiple fields, including parent fields, separated by commas.
- 3Filtering a parent field for non-null values ensures every returned contact actually has an associated account.
Hard
SOQLChild-to-Parent
29. SOQL Child-to-Parent: Contact with Account Owner (2-Level)
Problem #193 · Salesforce Apex Coding Challenge
Problem Statement
Write a query with two-level parent traversal: Contact → Account → Owner (User), retrieving the account owner's name and email.
Requirements
- Return each Contact's
Id, FirstName, LastName, and Email, plus the parent Account's Name and the Account owner's Name and Email.
- Only include contacts whose account owner has a name populated.
- Sort primarily by the account owner's name, then by the contact's LastName, and cap the results at 30 rows.
Approach
- 1Dot notation can chain across more than one relationship step to reach a grandparent field, such as the owner of a contact's account.
- 2Filters and sorts can also reference these multi-level relationship fields, not just top-level fields.
- 3Think about which field represents the 'grandparent' value you need to sort by.
Medium
SOQLParent-to-Child
30. SOQL Parent-to-Child: Account with Open Cases
Problem #196 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve Accounts that have open Cases, using a parent-to-child subquery for the cases and a semi-join to filter only accounts with open cases.
Requirements
- For each qualifying Account, return its
Id, Name, and Phone, plus a nested list of its non-closed cases' Id, CaseNumber, Subject, Status, and Priority, sorted by priority.
- Restrict the outer Accounts to only those that actually have at least one non-closed case, using a filter against the Case object.
- Sort the outer results alphabetically by Name and cap them at 10 rows.
Approach
- 1The subquery's relationship name is the plural form of the child object.
- 2A semi-join filters the outer object by checking membership against the result of an inner query on a related object — useful when you need to guarantee the parent actually has a matching child record.
- 3The semi-join ensures you only get Accounts that actually have matching case records, rather than accounts with an empty nested list.
Medium
SOQLParent-to-Child
31. SOQL Parent-to-Child: Account with Minimal Contact Subquery
Problem #197 · Salesforce Apex Coding Challenge
Problem Statement
Write a parent-to-child query that retrieves Accounts with only the Contact Id in the subquery — the minimal useful subquery pattern.
Requirements
- Return each qualifying Account's
Id, Name, and Type, plus just the Id of each related contact in a nested list.
- Restrict the results to Accounts that actually have at least one related contact.
- Sort alphabetically by Name and cap the results at 20 rows.
Approach
- 1A child subquery doesn't need to select many fields — sometimes just the Id is enough to prove a relationship exists.
- 2A semi-join against the child object lets you keep only parent records that actually have a matching child, filtering by the parent's own Id.
- 3The child subquery's relationship name is plural, while a semi-join filter on the child object itself uses the singular object name.
Medium
SOQLParent-to-Child
32. SOQL Parent-to-Child: Account with Contacts Filtered by Email
Problem #198 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve Accounts with a subquery for Contacts that have an email address. Filter and sort within the subquery.
Requirements
- Return each Account's
Id, Name, and Industry, plus a nested list of its contacts that have an email address on file, showing each contact's Id, FirstName, LastName, and Email.
- Sort the nested contacts alphabetically by LastName.
- Sort the outer Account results alphabetically by Name and cap them at 15 rows.
Approach
- 1A subquery can carry its own filter condition to restrict which child records are included in the nested list.
- 2Add a sort inside the subquery to order the nested child records independently of the outer query.
- 3Accounts without any contacts that meet the filter will simply have an empty nested list, not an error.
Medium
SOQLParent-to-Child
33. SOQL Parent-to-Child: Account with Opportunities Ordered by Close Date
Problem #199 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve high-revenue Accounts with their Opportunities sorted by close date. Combines outer-query filtering with inner-subquery sorting.
Requirements
- Return each Account's
Id, Name, and AnnualRevenue, plus a nested list of its opportunities' Id, Name, StageName, Amount, and CloseDate, sorted by close date (soonest first).
- Restrict the outer results to Accounts with a positive annual revenue.
- Sort the outer results by AnnualRevenue from highest to lowest, and cap them at 10 rows.
Approach
- 1A subquery's sort order operates independently of the outer query's sort order — each can order by a different field and direction.
- 2The outer query's sort can be on a totally different field than the one used to filter it.
- 3A simple numeric comparison filter can restrict the outer query to accounts above a threshold.
Medium
SOQLParent-to-Child
34. SOQL Parent-to-Child: Account with Contacts Sorted NULLS LAST
Problem #200 · Salesforce Apex Coding Challenge
Problem Statement
Retrieve Accounts with a Contacts subquery that sorts contacts by LastName using the NULLS LAST clause to handle contacts with no last name gracefully.
Requirements
- Return each Account's
Id, Name, and Type, plus a nested list of its contacts' Id, FirstName, LastName, and Title.
- Sort the nested contacts alphabetically by LastName, placing contacts with no last name at the end of that list rather than the beginning.
- Sort the outer Account results alphabetically by Name and cap them at 15 rows.
Approach
- 1SOQL has a modifier for controlling where null values land in a sort order, appended after the ASC/DESC keyword.
- 2By default, ascending sorts place nulls first — you can override that placement explicitly.
- 3This modifier can be applied inside a child subquery's ORDER BY just like in an outer query.
Hard
SOQLParent-to-Child
35. SOQL Parent-to-Child: Account with Two Subqueries (Contacts + Opportunities)
Problem #201 · Salesforce Apex Coding Challenge
Problem Statement
Write a query with two parent-to-child subqueries in a single SELECT, retrieving both Contacts and Opportunities for each Account.
Requirements
- Include a nested list of each Account's contacts (
Id, FirstName, LastName, Email), sorted alphabetically by LastName.
- Also include a nested list of each Account's still-open opportunities (
Id, Name, StageName, Amount).
- Return each Account's
Id, Name, Industry, and AnnualRevenue, restricted to Accounts with a populated Name, sorted alphabetically by Name and capped at 10 rows.
Approach
- 1A single outer query can include more than one child subquery, separated by commas in the field list.
- 2Each subquery can have its own independent filter, sort, and limit.
- 3Keep the two subqueries targeting different related objects distinct from each other.
Hard
SOQLParent-to-Child
36. SOQL Parent-to-Child: Account with Cases and Case Comments
Problem #202 · Salesforce Apex Coding Challenge
Problem Statement
Write a three-level nested query: Account → Cases → CaseComments, using two levels of parent-to-child subqueries.
Requirements
- Within each case's nested list, include a further nested list of its comments (
Id, CommentBody, CreatedDate), newest first.
- For each Account, include a nested list of its non-closed cases (
Id, CaseNumber, Subject, Status), sorted by case number, each carrying the nested comments described above.
- Return each Account's
Id and Name, sorted alphabetically by Name and capped at 5 rows.
Approach
- 1SOQL supports nesting a subquery inside another subquery to traverse a grandchild relationship.
- 2The relationship name for case comments follows the same plural naming pattern as other child relationships.
- 3Build the innermost subquery first, then wrap it inside the subquery for its immediate parent object.
Hard
SOQLChild-to-ParentParent-to-Child
37. SOQL Combined: Child-to-Parent + Parent-to-Child
Problem #203 · Salesforce Apex Coding Challenge
Problem Statement
Write two SOQL queries in the same editor — one using child-to-parent, one using parent-to-child — demonstrating both relationship directions.
Query 1 — Child-to-Parent
- Return each open Opportunity's core fields along with its parent Account's Name and the account owner's Name, sorted by Amount from highest to lowest and capped at 20 rows.
Query 2 — Parent-to-Child
- Return Accounts with a nested list of their contacts' core fields (sorted by LastName), capped at 10 rows.
Approach
- 1Query 1: child-to-parent uses dot notation to traverse from a child record up through its parent (and grandparent) relationships.
- 2Query 2: parent-to-child uses a subquery in parentheses within the outer SELECT to embed related child records.
Medium
SOQL
38. SOQL: Aggregate — Accounts with 5 or More Contacts
Problem #215 · Salesforce Apex Coding Challenge
Problem Statement
Write a SOQL aggregate query that returns the AccountId and the
number of Contacts (COUNT(Id)) for every Account that has
5 or more Contacts.
Requirements
- Query the
Contact object, excluding Contacts with no Account.
- Group the results by Account so each Account appears once with its Contact count.
- Alias the count as
contactCount (the grading checks this exact alias).
- Only keep Accounts whose Contact count meets the 5-or-more threshold.
- Sort so the Accounts with the most Contacts appear first.
- Limit to 20 results.
Key Concepts
GROUP BY — collapses rows by a field value.
HAVING — filters after grouping (unlike WHERE which filters before).
- Aggregate functions:
COUNT(Id), SUM(), AVG(), MAX(), MIN().
Approach
- 1The count alias must be exactly contactCount — grading checks for it.
- 2Filtering on the group total (rather than individual rows) requires a clause that runs after grouping, not a plain WHERE.
- 3Sort using the same aggregate expression you grouped/filtered on, descending.
- 4HAVING vs WHERE: WHERE filters individual rows before grouping; HAVING filters groups after.
Medium
SOQL
39. SOQL: Semi-Join and Anti-Join Subqueries
Problem #216 · Salesforce Apex Coding Challenge
Problem Statement
Write two SOQL queries demonstrating semi-join and anti-join patterns.
Query 1 — Semi-Join (IN subquery)
- Select the
Id, FirstName, LastName, and Email of Contacts whose Account is in the Technology industry, using a semi-join rather than a dot-notation traversal.
- Sort alphabetically by LastName and cap the results at 50 rows.
Query 2 — Anti-Join (NOT IN subquery)
- Select the
Id, Name, Industry, and AnnualRevenue of Accounts that have no related Contacts at all, using an anti-join.
- Sort alphabetically by Name and cap the results at 25 rows.
Key Rules
- The subquery in an anti-join must exclude records where the join field is null,
otherwise the NOT IN comparison returns no rows at all.
- Semi-join / anti-join subqueries can only return one field.
Approach
- 1A semi-join filters the outer object by checking that its Id (or a lookup field) appears in the result of an inner query on a related object.
- 2An anti-join is the semi-join's mirror image: it filters the outer object by checking that its Id does NOT appear in the inner query's result.
- 3Critical: the anti-join's inner subquery must filter out null values on the join field it selects — NOT IN with nulls in the comparison set always returns 0 rows.
- 4Subqueries in semi/anti-join can only SELECT a single Id field.
Medium
SOQL
40. SOQL: Dynamic SOQL with String.escapeSingleQuotes
Problem #228 · Salesforce Apex Coding Challenge
Problem Statement
Write AccountSearchService with a static method
searchByName(String searchTerm) that performs a dynamic SOQL search
on Account using LIKE.
Requirements
- Return an empty list if
searchTerm is blank.
- Sanitise input:
String.escapeSingleQuotes(searchTerm.trim()).
- Wrap with wildcards:
'%' + safeTerm + '%'.
- Build the SOQL string using string concatenation with a bind variable
(
:safeTerm).
- Execute with
Database.query(soql).
- Wrap in
try-catch(QueryException e) — return empty list on error.
- Return the fields
Id, Name, Industry, AnnualRevenue, Phone, capped at 50 records.
Best Practices
String.escapeSingleQuotes() is mandatory for dynamic SOQL
— prevents SOQL injection attacks.
- Catch
QueryException specifically, not generic Exception,
for SOQL errors.
- Return an empty list (not null) on error — callers should never receive null.
Approach
- 1Blank check: if (String.isBlank(searchTerm)) return new List();
- 2Escape: String safeTerm = '%' + String.escapeSingleQuotes(searchTerm.trim()) + '%';
- 3Bind variables work in dynamic SOQL strings too — reference the local `safeTerm` variable with a colon, and it is resolved at runtime.
- 4Execute: return (List) Database.query(soql); — cast is required.
Hard
SOQL
41. SOQL: Aggregate with CALENDAR_MONTH and FISCAL_YEAR Date Functions
Problem #229 · Salesforce Apex Coding Challenge
Problem Statement
Write two advanced aggregate SOQL queries using date functions.
Query 1 — Monthly Revenue Breakdown
- For each calendar month, return the total revenue and count of won Opportunities that closed in that month, aliasing the aggregated columns.
- Restrict the results to won opportunities that closed within the current fiscal year.
- Group by month and sort chronologically.
Query 2 — Deal Size Stats by Stage
- For each Stage, return the average, highest, and lowest Amount among closed opportunities.
- Restrict the results to opportunities that closed within the last 90 days.
- Group by stage and sort by the average deal size, largest first.
Key Concepts
CALENDAR_MONTH(), FISCAL_YEAR() — SOQL date functions
usable in SELECT and GROUP BY.
- SOQL provides relative date literals for "current fiscal year" and "the last N days" so you don't need to compute date boundaries yourself.
AVG(), MAX(), MIN() — aggregate functions.
Approach
- 1Date functions can appear in the SELECT list just like regular fields, and can be given an alias for a friendlier column name.
- 2SOQL has a date function for extracting the fiscal year of a date field, which can be compared against a relative date literal representing the current fiscal year — no bind variable needed.
- 3SOQL supports a relative date literal for "the last N days" using a colon-separated syntax, letting you avoid computing a date boundary manually.
- 4Whatever date function you group by must also appear (in the same form) in the SELECT list.
Medium
SOQLAggregate
42. SOQL: Sum of All Opportunity Amounts
Problem #260 · Salesforce Apex Coding Challenge
Problem Statement
Implement getTotalOpportunityAmount() that returns the
sum of the Amount field across all Opportunities.
Requirements
- Use
SUM(Amount) with alias totalAmount
- FROM
Opportunity
- Access the result with
results[0].get('totalAmount')
- Cast and return as
Decimal
Approach
- 1SUM(Amount) totalAmount — the alias is used when calling .get('totalAmount').
- 2AggregateResult[] holds the result; results[0] is the single summary row.
- 3Cast with (Decimal) since .get() returns Object.
Medium
SOQLAggregate
43. SOQL: Average Amount of All Opportunities
Problem #261 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAverageOpportunityAmount() that returns the
average of the Amount field across all Opportunities.
Requirements
- Use
AVG(Amount) with alias avgAmount
- FROM
Opportunity
- Access via
results[0].get('avgAmount')
- Return type:
Decimal
Approach
- 1AVG(Amount) calculates the mean; null Amount records are excluded from the average.
- 2Alias the result (avgAmount) so .get('avgAmount') retrieves it cleanly.
- 3AVG returns a Decimal — always cast the Object result.
Medium
SOQLAggregate
44. SOQL: Maximum Opportunity Amount
Problem #262 · Salesforce Apex Coding Challenge
Problem Statement
Implement getMaxOpportunityAmount() that returns the
largest Amount value across all Opportunities.
Requirements
- Use
MAX(Amount) with alias maxAmount
- FROM
Opportunity
- Access via
results[0].get('maxAmount')
- Return type:
Decimal
Approach
- 1MAX(Amount) finds the single highest value in the Amount column.
- 2Like all aggregate functions, MAX ignores null values.
- 3Pair with MIN() to get the full range.
Medium
SOQLAggregate
45. SOQL: Minimum Opportunity Amount
Problem #263 · Salesforce Apex Coding Challenge
Problem Statement
Implement getMinOpportunityAmount() that returns the
smallest Amount value across all Opportunities.
Requirements
- Use
MIN(Amount) with alias minAmount
- FROM
Opportunity
- Access via
results[0].get('minAmount')
- Return type:
Decimal
Approach
- 1MIN(Amount) finds the single lowest value in the Amount column.
- 2Null Amount records are excluded from MIN/MAX calculations.
- 3Both MIN and MAX work on numeric, date, and text fields.
Medium
SOQLAggregate
46. SOQL: Count Opportunities Grouped by Stage
Problem #264 · Salesforce Apex Coding Challenge
Problem Statement
Implement getOpportunityStageCount() that returns a
count of Opportunities grouped by StageName.
Requirements
- SELECT
StageName, COUNT(Id) totalCount
- FROM
Opportunity
- GROUP BY
StageName
- Return type:
List<AggregateResult>
Accessing Results
for (AggregateResult ar : results) {
String stage = (String) ar.get('StageName');
Integer cnt = (Integer) ar.get('totalCount');
}
Approach
- 1GROUP BY StageName splits rows into one summary row per unique stage.
- 2COUNT(Id) counts records in each group — alias it as totalCount.
- 3Every non-aggregate SELECT field must appear in the GROUP BY clause.
Medium
SOQLAggregate
47. SOQL: Filter Groups With HAVING Clause
Problem #265 · Salesforce Apex Coding Challenge
Problem Statement
Implement getStagesHavingMoreThanFiveOpps() that returns only
Opportunity stages that have more than 5 records.
Requirements
- SELECT
StageName, COUNT(Id) totalCount
- GROUP BY
StageName
- HAVING
COUNT(Id) > 5
Key Concept
HAVING filters after grouping (like a WHERE for aggregate results).
It cannot be replaced by WHERE because WHERE filters individual rows, not groups.
Approach
- 1HAVING filters groups — it operates on the result of GROUP BY.
- 2WHERE filters individual rows before grouping; HAVING filters groups after.
- 3HAVING COUNT(Id) > 5 keeps only stages with more than five opportunities.
Medium
SOQLRelationships
48. SOQL: Child-to-Parent Relationship — Contact to Account Name
Problem #266 · Salesforce Apex Coding Challenge
Problem Statement
Implement getContactsWithAccountName() that retrieves all Contacts
along with the parent Account's Name using a child-to-parent
relationship traversal.
Requirements
- Return the
Id, FirstName, and LastName of every Contact
- Also include the related Account's
Name, reached by traversing the lookup from Contact to Account
- Query the
Contact object
Dot Notation
Traverse up to 5 levels of parent relationships using dots:
Contact.Account.Owner.Profile.Name.
Approach
- 1Dot notation traverses parent relationships: Account.Name fetches the related Account's Name.
- 2The relationship name from Contact to Account is "Account" (same as the field name).
- 3Up to 5 levels deep is allowed: Contact.Account.Owner.Name.
Medium
SOQLRelationships
49. SOQL: Child-to-Parent Relationship — Opportunity to Account Name
Problem #267 · Salesforce Apex Coding Challenge
Problem Statement
Implement getOppsWithAccountName() that retrieves all Opportunities
along with the parent Account's Name.
Requirements
- Return the
Id and Name of every Opportunity
- Also include the related Account's
Name, reached by traversing the lookup to Account
- Query the
Opportunity object
Approach
- 1Opportunity has a standard lookup to Account via the AccountId field.
- 2Use Account.Name in the SELECT clause to traverse the relationship.
- 3The resulting Opportunity records will have opp.Account.Name populated.
Hard
SOQLRelationships
50. SOQL: Multi-Level Child-to-Parent — Opportunity Owner Profile
Problem #268 · Salesforce Apex Coding Challenge
Problem Statement
Implement getOpportunityOwnerProfile() that traverses
two levels of parent relationships to retrieve the Opportunity
owner's name and their Profile name.
Requirements
- Return the
Id and Name of every Opportunity
- Also include the owner's name and the owner's Profile name, reached by traversing two levels of parent relationships
- Query the
Opportunity object
Relationship Chain
An Opportunity's owner is a User, and that User has a related Profile —
chaining a relationship traversal across both hops lets you reach the Profile's
Name from the Opportunity.
Approach
- 1Look up the relationship name for the OwnerId lookup field on Opportunity — it lets you reach the owning User record.
- 2The User object has its own lookup to a Profile record — chain a second dot-notation hop off the first to reach it.
- 3Up to 5 levels of parent traversal are allowed in a single SOQL query.
Medium
SOQLSubquery
51. SOQL: Parent-to-Child Subquery — Account With Contacts
Problem #269 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAccountsWithContacts() using a
parent-to-child subquery to fetch each Account along with
its related Contacts in a single query.
Requirements
- Return each Account's
Id and Name
- For each Account, also include a nested list of its related Contacts, each with
Id, FirstName, and LastName
- Use the plural child relationship name when writing the nested query
Key Concept
The child relationship name is the plural of the child object:
Contacts, Opportunities, Cases, etc.
Approach
- 1Subqueries use the plural child relationship name: Contacts (not Contact).
- 2The subquery is wrapped in parentheses inside the outer SELECT.
- 3Access child records: for (Contact c : account.Contacts) { … }
Medium
SOQLSubquery
52. SOQL: Parent-to-Child Subquery — Account With Opportunities
Problem #270 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAccountsWithOpportunities() using a
parent-to-child subquery to retrieve each Account with its
related Opportunities.
Requirements
- Return each Account's
Id and Name
- For each Account, also include a nested list of its related Opportunities, each with
Id, Name, and Amount
- Note the plural child relationship name for Opportunity records
Approach
- 1The child relationship name from Account to Opportunity is "Opportunities" (plural).
- 2Subquery fields: Id, Name, Amount — keep it minimal.
- 3Access: for (Opportunity o : account.Opportunities) { … }
Medium
SOQLSubquery
53. SOQL: Parent-to-Child Subquery With WHERE Filter
Problem #271 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAccountsWithClosedWonOpps() that fetches every Account
together with only its Closed Won Opportunities using a filtered
parent-to-child subquery.
Requirements
- Return every Account's
Id and Name, regardless of its Opportunities
- For each Account, include a nested list of only its Opportunities that are in the "Closed Won" stage, each with
Id, Name, and StageName
Approach
- 1You can add a WHERE clause inside the child subquery to filter child records.
- 2Filter the nested subquery so only Opportunities in the "Closed Won" stage are returned.
- 3The outer Account records are all returned — only the child list is filtered.
Hard
SOQLSubquery
54. SOQL: Semi-Join — Accounts That Have Opportunities
Problem #273 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAccountsHavingOpps() using a semi-join
to return only Accounts that have at least one related Opportunity.
Requirements
- Return only the
Id and Name of Accounts that have at least one related Opportunity
- Filter the outer Account query by checking membership against an inner query on the Opportunity object that returns the linking
AccountId
Semi-Join vs Subquery
A semi-join uses IN (SELECT …) in the WHERE clause
to filter the outer object based on existence in a related object — no JOIN keyword
needed in SOQL.
Approach
- 1A semi-join filters the outer object using IN against an inner query on a related object — no outer JOIN needed.
- 2The inner SELECT must return the field that links back to the outer object (AccountId).
- 3SOQL supports up to 2 levels of subquery nesting.
Hard
SOQLSubquery
55. SOQL: Anti-Join — Accounts Without Any Opportunities
Problem #274 · Salesforce Apex Coding Challenge
Problem Statement
Implement getAccountsWithoutOpps() using an anti-join
to return only Accounts that have no related Opportunities.
Requirements
- Return only the
Id and Name of Accounts that have zero related Opportunities
- Filter the outer Account query by excluding any match against an inner query on the Opportunity object that returns the linking
AccountId
Anti-Join vs Semi-Join
- Semi-join:
IN (subquery) — records that have a match
- Anti-join:
NOT IN (subquery) — records that do NOT have a match
Approach
- 1NOT IN (subquery) is an anti-join — it returns the complement of a semi-join.
- 2A null AccountId in Opportunity can cause unexpected results with NOT IN — be aware.
- 3This pattern is essential for finding orphaned records (e.g., Accounts with no deals).
Medium
SOQLAggregate
56. SOQL: Total Opportunity Amount Grouped by Stage
Problem #276 · Salesforce Apex Coding Challenge
Problem Statement
Implement getOpportunityAmountByStage() that returns the
total Opportunity amount for each stage.
Requirements
- SELECT
StageName, SUM(Amount) totalAmount
- FROM
Opportunity
- GROUP BY
StageName
- Return type:
List<AggregateResult>
Accessing Results
Decimal total = (Decimal) ar.get('totalAmount');
String stage = (String) ar.get('StageName');
Approach
- 1Combine SUM with GROUP BY to get a total per stage.
- 2Alias the SUM: SUM(Amount) totalAmount so you can retrieve it with .get('totalAmount').
- 3Non-aggregate SELECT fields (StageName) must appear in GROUP BY.
Medium
SOQLDynamic SOQL
57. SOQL: Dynamic SOQL Using Database.query()
Problem #279 · Salesforce Apex Coding Challenge
Problem Statement
Implement getDynamicAccounts(String industryName) that builds
a SOQL query dynamically at runtime and executes it with
Database.query().
Requirements
- Build a SOQL query string, as text, that selects the
Id, Name, and Industry of Accounts filtered by the given industryName
- Use a bind variable so
industryName is substituted safely, not concatenated as raw text
- Execute the built string with
Database.query(queryStr)
- Return type:
List<Account>
Security Note
Using :variable bind syntax in dynamic SOQL is safe.
Never concatenate user input directly into the query string —
that creates SOQL injection vulnerabilities.
Approach
- 1Concatenate string parts to build the query: 'SELECT … ' + 'FROM Account ' + 'WHERE …'.
- 2Bind variables in dynamic SOQL still use :variableName — they are resolved from the Apex scope.
- 3Database.query(queryStr) executes the dynamic string and returns a List; cast as needed.
Medium
SOQL
58. SOQL: Count Distinct Opportunity Owners per Stage
Problem #396 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class OpportunityOwnerStageQuery with a method
getDistinctOwnerCountByStage() that returns, for each
StageName, the number of distinct Opportunity Owners who have
an Opportunity in that stage.
Requirements
- Use
COUNT_DISTINCT(OwnerId) — a plain COUNT(OwnerId) would
count every Opportunity, not every unique owner.
- Group the results by
StageName, aliasing both the grouped field and the aggregate.
- Return type is
List<AggregateResult>.
Best Practices
COUNT_DISTINCT vs plain COUNT is a common SOQL gotcha — pick
the one that actually answers the business question ("how many opportunities" vs "how many
different owners").
- Alias both the grouped field and the aggregate expression so calling code can read
results by name instead of positional index.
- Order grouped results deterministically (
ORDER BY StageName ASC) so output
doesn't depend on database internals.
Approach
- 1COUNT_DISTINCT(OwnerId) counts unique owners; COUNT(OwnerId) would count every Opportunity row instead.
- 2Alias the grouped field (stage) and the aggregate (ownerCount) so results are easy to read by name.
- 3The field you group by must be the exact same field you aliased as stage in the SELECT list.
Hard
SOQL
59. SOQL: Multi-Field GROUP BY With HAVING — High-Revenue Stage/Type Combinations
Problem #397 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class StageTypeRevenueQuery with a method
getHighRevenueStageTypeCombos() that groups Opportunities by
both StageName and Type, totals the
Amount for each combination, and returns only combinations whose total exceeds
a revenue threshold.
Requirements
- Group by both
StageName and Type together — two grouping
fields, not one.
- Keep only combinations whose summed
Amount exceeds the threshold, where
the threshold is a named constant, not a raw literal in the query.
- Order the result by total descending, so the highest-revenue combination comes first.
Best Practices
- Grouping by more than one field is straightforward — just list every grouping column,
separated by commas, in both
SELECT and GROUP BY.
HAVING filters on the aggregated value; a plain WHERE
cannot reference SUM(Amount).
- A business-meaningful threshold like this belongs in a named constant
(
REVENUE_THRESHOLD) rather than a bare number, even inside a query.
Approach
- 1List every grouping field, comma-separated, in both the SELECT list and the grouping clause.
- 2HAVING comes after the grouping clause and can reference the aggregate function directly to filter on the summed total against the threshold constant.
- 3You can order by an aggregate expression the same way you would a regular field, sorting the largest totals first.
Hard
SOQL
60. SOQL: Leads Without a Matching Converted Contact by Email
Problem #398 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class UnmatchedLeadQuery with a method
getLeadsWithoutMatchingContact() that returns every non-converted Lead whose
Email does not match any existing Contact's Email.
Requirements
- Use an anti-join (
NOT IN with a subquery) against Contact's Email field — not an Apex loop.
- Filter out Leads with a null
Email, and filter the inner subquery to
Email != null Contacts too.
- Only consider non-converted Leads (
IsConverted = false).
Best Practices — the NOT IN / NULL pitfall
- In SQL-family languages (SOQL included),
X NOT IN (subquery) that returns
even one null makes the whole comparison evaluate to
unknown/false for every row — the outer query would silently return nothing. Filtering the
subquery to Email != null avoids this trap entirely.
- Guarding the outer
Email != null too avoids comparing a null Lead email
against the subquery at all.
WITH SECURITY_ENFORCED plus LIMIT round out the query as
production-safe.
Approach
- 1A NOT IN subquery that can return a null value makes the whole WHERE clause match nothing — always filter the subquery's column to != null.
- 2Guard the outer Email != null too, so Leads with no email at all aren't evaluated against the subquery.
- 3IsConverted = false keeps already-converted Leads (which already became Contacts) out of scope.
Medium
SOQL
61. SOQL: Case Count by Priority for a Specific Account
Problem #407 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class CasePriorityCountQuery with a method
getCaseCountByPriority(Id accountId) that returns, for a single Account, how
many Cases exist at each Priority level.
Requirements
- Scope the aggregation to a single Account, identified by the
accountId
parameter, before grouping.
- Group the results by
Priority, aliasing both the grouped field and the count.
Best Practices
- Filtering with
WHERE before an aggregate GROUP BY scopes the
aggregation to exactly the records you care about — the WHERE clause applies
before grouping, not after.
- Alias both the grouped dimension and the aggregate so results read naturally by name.
Approach
- 1Filtering to the given Account before grouping ensures only that Account's Cases are counted per Priority.
- 2Alias the grouped field (pri) and the aggregate (caseCount) so the AggregateResult keys are readable.
- 3ORDER BY Priority ASC gives deterministic, alphabetically sorted output.
Medium
SOQL
62. SOQL: Contacts With Their Account's Annual Revenue
Problem #408 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ContactAccountRevenueQuery with a method
getContactsWithAccountRevenue() that returns every Contact together with its
parent Account's Name and AnnualRevenue, using a
child-to-parent relationship query.
Requirements
- Traverse the relationship with dot notation:
Account.Name,
Account.AnnualRevenue — no second query needed.
- Only include Contacts that actually have a parent Account.
Best Practices
- A single child-to-parent dot-notation query avoids an unnecessary second SOQL query
and a manual map-join — the platform resolves the relationship for you.
- Excluding Contacts with no parent Account guards against every
Account.*
field coming back null on those rows.
Approach
- 1Account.Name and Account.AnnualRevenue can be selected directly with dot notation — no second query required.
- 2Excluding Contacts whose AccountId is null avoids returning empty relationship fields for orphaned Contacts.
- 3This still returns List — the parent fields just come along for the ride on each row.
Medium
SOQL
63. SOQL: Each Account's Highest-Value Open Opportunity
Problem #409 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class AccountTopOpportunityQuery with a method
getAccountsWithTopOpenOpportunity() that returns every Account together with
(at most) its single highest-value open Opportunity, using a parent-to-child
subquery.
Requirements
- The subquery must consider only Opportunities that are still open, and narrow that
set down to just the single one with the largest
Amount per Account.
- Return type is
List<Account> — each Account's related list will
contain zero or one Opportunity.
Best Practices
- A subquery can carry its own independent
WHERE, ORDER BY, and
LIMIT — this is the standard way to fetch "top N related records per parent"
without a second query or in-memory sorting.
- This single query replaces what would otherwise require querying Accounts, then looping
to query each Account's Opportunities separately — exactly the kind of query-in-a-loop this
pattern avoids.
Approach
- 1A parent-to-child subquery goes inside parentheses right in the SELECT list, referencing the child relationship name.
- 2The subquery can define its own filtering, sorting, and row cap that are entirely independent of the outer query's clauses.
- 3This returns List — access each Account's matched Opportunity via its Opportunities relationship list in calling code.
Hard
SOQL
64. SOQL: Two-Level Child-to-Parent — Contact's Account Owner Name
Problem #410 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ContactAccountOwnerQuery with a method
getContactsWithAccountOwner() that returns every Contact together with the
Name and Email of its parent Account's Owner — a two-level
child-to-parent traversal (Contact → Account → Owner).
Requirements
- Use a two-level dot-notation path:
Account.Owner.Name,
Account.Owner.Email.
- Guard against Contacts with no parent Account.
Best Practices
- SOQL supports traversing up to 5 levels of parent relationships via dot notation —
Account.Owner.Name is a two-level hop (Contact → Account → Owner), all
resolved in a single query with no additional round trips.
- The same null-Account guard matters even more here, since a null Account also means a
null Owner two levels up.
Approach
- 1Chain the relationship two levels deep: Account.Owner.Name reaches the User who owns the Contact's Account.
- 2SOQL allows up to 5 levels of parent-relationship traversal via dot notation in a single query.
- 3The null-Account guard still applies here — without a parent Account there is no Owner to reach either.
Medium
SOQL
65. SOQL: Opportunities Missing a Next Step
Problem #411 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class OpenOpportunityGapsQuery with a method
getOpenOpportunitiesMissingNextStep() that returns every open Opportunity that
has no NextStep defined — a common "sales hygiene" report.
Requirements
- Only open Opportunities should be included.
- Only those with a blank/null
NextStep.
Best Practices
- In SOQL, comparing a field to
null is written as Field = null
(or != null) — this is the SOQL equivalent of Apex's
String.isBlank() check, since SOQL doesn't have a text-comparison function for
blank/whitespace-only text fields.
- Combining the open-status check with the blank check in one query avoids a
wasteful "fetch everything then filter in Apex" pattern.
Approach
- 1Filtering on the standard open/closed status field scopes the query to open Opportunities only.
- 2A blank text field is checked in SOQL with a null comparison — there is no ISBLANK() function in SOQL itself.
- 3Combine both conditions with AND in a single WHERE clause.
Hard
SOQL
66. SOQL: Accounts With More Than One Open Opportunity (GROUP BY + HAVING)
Problem #412 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class MultiOpenOpportunityAccountsQuery with a method
getAccountsWithMultipleOpenOpportunities() that returns each Account's open
Opportunity count, but only for Accounts with more than one open
Opportunity — ranked from most to fewest.
Requirements
- Filter to open Opportunities with
WHERE IsClosed = false
before grouping.
GROUP BY AccountId, then use HAVING COUNT(Id) > 1 to keep
only multi-opportunity Accounts.
- Order by the count, descending.
Best Practices — WHERE vs. HAVING
WHERE filters individual rows before they're grouped;
HAVING filters groups after aggregation. You cannot write
WHERE COUNT(Id) > 1 — the aggregate doesn't exist yet at the row-filtering
stage, which is exactly why HAVING exists as a separate clause.
- Ordering by the same aggregate expression used in
HAVING keeps the most
significant Accounts at the top of the result.
Approach
- 1WHERE IsClosed = false filters rows before grouping; it cannot reference COUNT(Id).
- 2HAVING COUNT(Id) > 1 filters the grouped results after aggregation — that's the only place a condition on an aggregate can go.
- 3ORDER BY COUNT(Id) DESC works even though the SELECT list aliased it as oppCount — both refer to the same aggregate.
Hard
SOQL
67. SOQL: Accounts With Both Open Opportunities and Contacts
Problem #414 · Salesforce Apex Coding Challenge
Problem Statement
Write an Apex class ActiveAccountsWithContactsQuery with a method
getActiveAccountsWithContacts() that returns every Account that
both has at least one open Opportunity and has at least one
Contact — along with the list of those Contacts.
Requirements
- Use a semi-join against Opportunity to require at least one open Opportunity.
- Use a second, separate semi-join against Contact to require at least one Contact.
- Also include each qualifying Account's Contacts via a parent-to-child subquery in the
SELECT list.
Best Practices
- Two semi-joins combined with
AND is how SOQL expresses "must have a
related record satisfying condition A and a related record satisfying condition
B" without pulling everything into Apex and filtering there.
- The parent-to-child subquery that shapes the returned Contacts data is independent
from the semi-join subqueries in the
WHERE clause — one shapes the returned
data, the others just filter which Accounts qualify.
Approach
- 1A semi-join filters the outer object by whether a related child record exists — no Apex loop needed.
- 2Combine two semi-joins with AND to require both conditions to hold at once.
- 3The Contacts subquery that shapes the outer SELECT list is separate from the semi-join subqueries used for filtering.
Hard
SOQL
68. Scenario: Multi-Currency-Aware Opportunity Revenue Rollup
Problem #445 · Salesforce Apex Coding Challenge
Business Scenario
This org has multi-currency enabled — Closed Won Opportunities are recorded in whatever
currency the deal was closed in. Finance needs one combined total in USD across every
currency.
Requirements
- Write an Apex class
MultiCurrencyRevenueQuery with a method
getTotalWonRevenueInUSD(Map<String, Decimal> rateToUsdByCurrencyCode)
taking a currency-code-to-USD-conversion-rate map (e.g. {'EUR' => 1.08}).
- Aggregate Closed Won
Amount, grouped by
CurrencyIsoCode, in a single query.
- Convert each currency's subtotal to USD using the supplied rate map, and sum the results.
- If a currency code isn't in the rate map, treat its rate as
1 (a safe
fallback) rather than throwing.
Best Practices
- Aggregating separately per currency is the standard way to handle amounts
in a multi-currency org — summing raw numbers across different currencies would
silently treat them as if they were the same unit.
- Currency conversion belongs in Apex (using externally-supplied, up-to-date rates), not
hardcoded — this method accepts the rate map as a parameter rather than hardcoding rates.
Approach
- 1Aggregating separately per currency code keeps each currency's Closed Won total distinct — never mix raw amounts across currencies.
- 2rateToUsdByCurrencyCode.containsKey(currencyCode) lets you fall back to a rate of 1 for any currency the caller didn't supply a rate for.
- 3Accumulate subtotal * rate into a running total across every currency group.
About ApexArena
ApexArena is a free, browser-based Salesforce Apex coding practice platform covering every major topic tested on the Salesforce Platform Developer I (PD1) and Platform Developer II (PD2) certification exams. All problems run directly in your browser with instant pass/fail feedback, best-practice linting (SOQL in loops, DML in loops, empty catch blocks), and governor limit monitoring — no Salesforce Developer Edition org required.
Related tutorials:
Apex Triggers ·
SOQL ·
Batch Apex ·
Interview Q&A ·
Governor Limits