This guide walks through 57 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 — for Contact on Account,
the relationship name is Contacts (plural).
SELECT Id, Name, (SELECT Id, FirstName, LastName FROM Contacts)
FROM Account
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).
- 2Syntax: SELECT Id, Name, (SELECT Id, LastName FROM Contacts) FROM Account ORDER BY 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 AccountId IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Closed Won')
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
- 1Semi-join syntax: WHERE AccountId IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Closed Won')
- 2The subquery field (AccountId) 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 AccountId FROM Case WHERE Status != 'Closed')
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
- 1Anti-join syntax: WHERE Id NOT IN (SELECT AccountId FROM Case WHERE Status != 'Closed')
- 2The subquery field (AccountId) must be an Id-type field that matches the outer WHERE field (Id).
- 3AccountId can be null on some Cases — to be safe, also add AND AccountId != null in 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 AccountId, COUNT(Id) cnt
FROM Contact
GROUP BY AccountId
HAVING COUNT(Id) > 2
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: 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
- Outer: SELECT
Id, Name, Email FROM Contact
- Semi-join:
WHERE AccountId IN (SELECT AccountId FROM Opportunity WHERE Name = :opportunityName)
- Method parameter:
String opportunityName
Approach
- 1Contacts are not directly linked to Opportunities — use Account as the bridge.
- 2Semi-join: WHERE AccountId IN (SELECT AccountId FROM Opportunity WHERE Name = :opportunityName).
- 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
- Outer: SELECT
Id, Name, Industry FROM Account
- Semi-join:
WHERE Id IN (SELECT AccountId FROM Opportunity WHERE Amount > 50000)
- on outer query
Approach
- 1Semi-join: WHERE Id IN (SELECT AccountId FROM Opportunity WHERE Amount > 50000).
- 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
- Outer: SELECT
Id, Name, Email FROM Contact
- Semi-join:
WHERE AccountId IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Proposal/Price Quote')
Approach
- 1Contact is not directly linked to Opportunity — use Account as the bridge.
- 2Semi-join: WHERE AccountId IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Proposal/Price Quote').
- 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
WHERE Id IN (SELECT AccountId FROM Opportunity)
AND Id IN (SELECT AccountId FROM Case)
- Single SOQL statement — no Apex loops
Approach
- 1Chain two semi-joins: WHERE Id IN (...) AND Id IN (...).
- 2First subquery: SELECT AccountId FROM Opportunity.
- 3Second subquery: SELECT AccountId FROM Case.
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 — Cross-object Field Comparison
Use dot-notation to traverse the Account relationship from Contact:
Account.BillingState in both SELECT and WHERE.
Requirements
- SELECT
Id, Name, MailingState, Account.BillingState
- WHERE
MailingState != Account.BillingState
- Null guards:
AND MailingState != null AND Account.BillingState != null
Approach
- 1Traverse the Account lookup with dot-notation: Account.BillingState.
- 2WHERE MailingState != Account.BillingState performs a cross-object field comparison.
- 3Add null guards so comparisons only run when both fields are populated.
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 average:
SELECT AVG(Amount) avgAmt FROM Opportunity
- Collect AccountIds:
SELECT AccountId FROM Opportunity WHERE Amount > :avgAmt
- Fetch Contacts:
SELECT Name, Email FROM Contact WHERE AccountId IN :accountIds
Governor Rules
- No SOQL inside loops — collect IDs first, then query
- Maximum 3 SOQL queries total
Approach
- 1Step 1 — AVG aggregate: SELECT AVG(Amount) avgAmt FROM Opportunity.
- 2Step 2 — Bind in WHERE: Amount > :avgAmt uses the computed value.
- 3Step 3 — Collect into Set, then query Contact WHERE AccountId IN :accountIds.
- 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
- WHERE
Approval_Status__c = 'Pending'
- SELECT includes
Approval_Status__c
- ORDER BY
Amount DESC
Approach
- 1Custom fields always use the __c suffix: Approval_Status__c.
- 2WHERE Approval_Status__c = 'Pending' is the exact filter.
- 3Include Approval_Status__c in SELECT 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
- WHERE
Priority__c = 'High' AND Amount > 50000
- SELECT includes
Priority__c
- ORDER BY
Amount DESC
Approach
- 1Custom fields use __c suffix: Priority__c.
- 2WHERE Priority__c = 'High' AND Amount > 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
- Outer: SELECT
Id, Name, Industry FROM Account
- Semi-join:
WHERE Id IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Prospecting')
- ORDER BY
Name ASC
Approach
- 1Semi-join: WHERE Id IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Prospecting').
- 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
- SELECT:
Id, Name, StageName, Amount, Account.Name, Account.Owner.Name
- FROM
Opportunity WHERE IsClosed = false
- ORDER BY
Account.Name ASC, LIMIT 20
Key Concept
SOQL supports up to 5 levels of parent traversal using chained dot notation: Account.Owner.Name goes Opportunity → Account → User (Owner).
Approach
- 1Two-level traversal: Account.Owner.Name (Opportunity → Account → User)
- 2SOQL allows up to 5 levels of parent traversal
- 3Filter open opportunities: WHERE IsClosed = false
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
- SELECT:
Id, FirstName, LastName, Email, Phone, Account.Name, Account.Type
- FROM
Contact WHERE Account.Type = 'Customer'
- ORDER BY
LastName ASC, LIMIT 50
Approach
- 1Filter on a parent field using WHERE Account.Type = 'Customer'
- 2SELECT Account.Type to display the type 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
- SELECT:
Id, CaseNumber, Subject, Status, Contact.FirstName, Contact.LastName, Contact.Email, Account.Name, Account.Phone
- FROM
Case WHERE Status != 'Closed'
- ORDER BY
CaseNumber ASC, LIMIT 20
Approach
- 1Case has two parent lookups: ContactId (to Contact) and AccountId (to Account)
- 2Use Contact.FirstName, Contact.LastName for the contact, Account.Name for the account
- 3Both parent traversals in one 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
- SELECT:
Id, Name, StageName, Amount, CloseDate, Account.Name, Account.AnnualRevenue
- FROM
Opportunity WHERE Account.AnnualRevenue >= 1000000 AND IsClosed = false
- ORDER BY
Account.AnnualRevenue DESC, LIMIT 20
Approach
- 1Filter on a numeric parent field: WHERE Account.AnnualRevenue >= 1000000
- 2Combine two conditions with AND
- 3ORDER BY Account.AnnualRevenue DESC 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
- SELECT:
Id, FirstName, LastName, Email, Phone, Title, Account.Name, Account.Type, Account.BillingCity, Account.Website
- FROM
Contact WHERE Account.Name != null
- ORDER BY
Account.Name ASC, LastName ASC, LIMIT 50
Approach
- 1Select multiple parent fields: Account.Name, Account.Type, Account.BillingCity, Account.Website
- 2Multi-field ORDER BY: ORDER BY Account.Name ASC, LastName ASC
- 3Filter nulls: WHERE Account.Name != null ensures every contact has an 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
- SELECT:
Id, FirstName, LastName, Email, Account.Name, Account.Owner.Name, Account.Owner.Email
- FROM
Contact WHERE Account.Owner.Name != null
- ORDER BY
Account.Owner.Name ASC, LastName ASC, LIMIT 30
Approach
- 1Two-level: Account.Owner.Name — Contact → Account → User (Owner)
- 2You can also filter on two-level fields: WHERE Account.Owner.Name != null
- 3ORDER BY a two-level field: ORDER BY Account.Owner.Name ASC
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
- Subquery:
(SELECT Id, CaseNumber, Subject, Status, Priority FROM Cases WHERE Status != 'Closed' ORDER BY Priority ASC)
- Outer WHERE:
Id IN (SELECT AccountId FROM Case WHERE Status != 'Closed')
- ORDER BY
Name ASC, LIMIT 10
Approach
- 1Subquery relationship name: Cases (plural)
- 2Semi-join: WHERE Id IN (SELECT AccountId FROM Case WHERE ...)
- 3The semi-join ensures you only get Accounts that actually have open cases
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
- Subquery:
(SELECT Id FROM Contacts)
- Outer WHERE:
Id IN (SELECT AccountId FROM Contact)
- ORDER BY
Name ASC, LIMIT 20
Approach
- 1Minimal subquery: (SELECT Id FROM Contacts)
- 2Semi-join to filter accounts that have contacts: WHERE Id IN (SELECT AccountId FROM Contact)
- 3Subquery uses plural (Contacts), semi-join uses singular (Contact)
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
- Subquery:
(SELECT Id, FirstName, LastName, Email FROM Contacts WHERE Email != null ORDER BY LastName ASC)
- Outer: FROM
Account, ORDER BY Name ASC, LIMIT 15
Approach
- 1Subquery: (SELECT ... FROM Contacts WHERE Email != null)
- 2Add ORDER BY inside the subquery to sort contacts alphabetically
- 3Accounts without email contacts will have an empty Contacts list
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
- Subquery:
(SELECT Id, Name, StageName, Amount, CloseDate FROM Opportunities ORDER BY CloseDate ASC)
- Outer: FROM
Account WHERE AnnualRevenue > 0, ORDER BY AnnualRevenue DESC, LIMIT 10
Approach
- 1Subquery: (SELECT ... FROM Opportunities ORDER BY CloseDate ASC)
- 2Outer ORDER BY is independent of subquery ORDER BY
- 3Filter outer query: WHERE AnnualRevenue > 0
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
- Subquery:
(SELECT Id, FirstName, LastName, Title FROM Contacts ORDER BY LastName ASC NULLS LAST)
- Outer: FROM
Account, ORDER BY Name ASC, LIMIT 15
Approach
- 1NULLS LAST puts records with null LastName at the end of results
- 2Full syntax: ORDER BY LastName ASC NULLS LAST
- 3NULLS FIRST (default for ASC) / NULLS LAST are SOQL-specific keywords
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
- First subquery:
(SELECT Id, FirstName, LastName, Email FROM Contacts ORDER BY LastName ASC)
- Second subquery:
(SELECT Id, Name, StageName, Amount FROM Opportunities WHERE IsClosed = false)
- Outer: FROM
Account, ORDER BY Name ASC, LIMIT 10
Approach
- 1Two subqueries separated by a comma in the outer SELECT
- 2Each subquery can have its own WHERE, ORDER BY, LIMIT
- 3Subquery 1: FROM Contacts | Subquery 2: FROM Opportunities
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
- Inner subquery:
(SELECT Id, CommentBody, CreatedDate FROM CaseComments ORDER BY CreatedDate DESC)
- Outer subquery:
(SELECT Id, CaseNumber, Subject, Status, <inner> FROM Cases WHERE Status != 'Closed' ORDER BY CaseNumber ASC)
- Outer: FROM
Account, ORDER BY Name ASC, LIMIT 5
Approach
- 1Three levels: Account → Cases → CaseComments
- 2CaseComments is the relationship name (plural) for CaseComment records
- 3The inner subquery (CaseComments) is nested inside the outer subquery (Cases)
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
- SELECT
Account.Name, Account.Owner.Name FROM Opportunity WHERE IsClosed = false, ORDER BY Amount DESC, LIMIT 20
Query 2 — Parent-to-Child
- SELECT
(SELECT Id, FirstName, LastName, Email, Phone FROM Contacts ORDER BY LastName ASC) FROM Account, LIMIT 10
Approach
- 1Query 1: child-to-parent uses dot notation (Account.Name, Account.Owner.Name)
- 2Query 2: parent-to-child uses subquery in parentheses (SELECT ... FROM Contacts)
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.
- Filter out Contacts with no Account:
WHERE AccountId != null.
- Group by
AccountId.
- Apply
HAVING COUNT(Id) >= 5.
- Order by count descending.
- 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
- 1Alias: SELECT AccountId, COUNT(Id) contactCount
- 2HAVING filters aggregate results: HAVING COUNT(Id) >= 5
- 3ORDER BY with aggregate: ORDER BY COUNT(Id) DESC
- 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 Contacts whose Account is in the
Technology industry.
WHERE AccountId IN (SELECT Id FROM Account WHERE Industry = 'Technology')
- ORDER BY
LastName ASC, LIMIT 50.
Query 2 — Anti-Join (NOT IN subquery)
- Select Accounts that have no Contacts.
WHERE Id NOT IN (SELECT AccountId FROM Contact WHERE AccountId != null)
- ORDER BY
Name ASC, LIMIT 25.
Key Rules
- The subquery in an anti-join must filter out nulls:
WHERE AccountId != null — otherwise the NOT IN returns no rows.
- Semi-join / anti-join subqueries can only return one field.
Approach
- 1Semi-join: WHERE AccountId IN (SELECT Id FROM Account WHERE Industry = 'Technology')
- 2Anti-join: WHERE Id NOT IN (SELECT AccountId FROM Contact WHERE AccountId != null)
- 3Critical: always add WHERE AccountId != null in the anti-join subquery — NOT IN with nulls 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.
- SELECT:
Id, Name, Industry, AnnualRevenue, Phone; LIMIT 50.
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 variable in dynamic SOQL: 'WHERE Name LIKE :safeTerm' — the local variable 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
- SELECT
CALENDAR_MONTH(CloseDate), SUM(Amount),
COUNT(Id) FROM Opportunity.
- Filter:
IsWon = true AND
FISCAL_YEAR(CloseDate) = THIS_FISCAL_YEAR.
- GROUP BY
CALENDAR_MONTH(CloseDate), ORDER BY month ASC.
Query 2 — Deal Size Stats by Stage
- SELECT
StageName, AVG(Amount), MAX(Amount),
MIN(Amount) FROM Opportunity.
- Filter:
CloseDate >= LAST_N_DAYS:90 AND IsClosed = true.
- GROUP BY
StageName, ORDER BY AVG(Amount) DESC.
Key Concepts
CALENDAR_MONTH(), FISCAL_YEAR() — SOQL date functions
usable in SELECT and GROUP BY.
THIS_FISCAL_YEAR, LAST_N_DAYS:N — date literals.
AVG(), MAX(), MIN() — aggregate functions.
Approach
- 1Date function in SELECT: SELECT CALENDAR_MONTH(CloseDate) closeMonth — can be aliased.
- 2Fiscal year filter: FISCAL_YEAR(CloseDate) = THIS_FISCAL_YEAR — no bind variable needed.
- 3Last N days: LAST_N_DAYS:90 — note the colon syntax for parameterised date literals.
- 4Group by date function: GROUP BY CALENDAR_MONTH(CloseDate) — must match what's in SELECT.
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
- SELECT
Id, FirstName, LastName, Account.Name
- FROM
Contact
- Access the related field with dot notation:
contact.Account.Name
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
- SELECT
Id, Name, Account.Name
- FROM
Opportunity
- Use dot notation to traverse the lookup to Account
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
- SELECT
Id, Name, Owner.Name, Owner.Profile.Name
- FROM
Opportunity
- Traverse: Opportunity → User (Owner) → Profile
Relationship Chain
Opportunity.OwnerId → User.ProfileId → Profile.Name
Approach
- 1Owner is the relationship name for the OwnerId lookup field on Opportunity.
- 2Owner.Profile traverses from User to its related Profile record.
- 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
- Outer: SELECT
Id, Name FROM Account
- Subquery:
(SELECT Id, FirstName, LastName FROM Contacts)
- Use the plural child relationship name
Contacts
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
- Outer: SELECT
Id, Name FROM Account
- Subquery:
(SELECT Id, Name, Amount FROM Opportunities)
- Note the plural child relationship name:
Opportunities
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
- Outer: SELECT
Id, Name FROM Account
- Subquery:
(SELECT Id, Name, StageName FROM Opportunities WHERE StageName = 'Closed Won')
Approach
- 1You can add a WHERE clause inside the child subquery to filter child records.
- 2Use WHERE StageName = 'Closed Won' inside (SELECT … FROM Opportunities …).
- 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
- Outer: SELECT
Id, Name FROM Account
- WHERE
Id IN (SELECT AccountId FROM Opportunity)
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
- 1Semi-join: WHERE Id IN (SELECT AccountId FROM Opportunity) — 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
- Outer: SELECT
Id, Name FROM Account
- WHERE
Id NOT IN (SELECT AccountId FROM Opportunity)
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 the query string:
'SELECT Id, Name, Industry FROM Account WHERE Industry = :industryName'
- Execute 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.
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