SOQL & SOSL

Medium & Hard SOQL & SOSL Practice Problems

57 free Salesforce Apex challenges · Full problem statements, best-practice notes, and step-by-step hints · Solve live on ApexArena

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.

On this page
  1. 1. Nth Highest Salary Finder
  2. 2. Dynamic SOQL Builder
  3. 3. Parent-to-Child Relationship Query
  4. 4. Semi-Join: Contacts at Accounts with Won Deals
  5. 5. Anti-Join: Accounts with No Open Cases
  6. 6. GROUP BY + HAVING: Accounts with More than 2 Contacts
  7. 7. Total Opportunities Per Account
  8. 8. Contacts Associated with a Specific Opportunity
  9. 9. Accounts with Opportunities Greater Than $50,000
  10. 10. Contacts on Opportunities in the Proposal Stage
  11. 11. Account with the Maximum Number of Opportunities
  12. 12. Accounts That Have Both Opportunities and Cases
  13. 13. Opportunities Not Updated in the Last 30 Days
  14. 14. Contacts Whose Mailing State Differs from Their Account's Billing State
  15. 15. Account Names with Total Amount of All Associated Opportunities
  16. 16. Contacts Related to Opportunities with Above-Average Amount
  17. 17. Top 3 Opportunities with the Highest Amount for Each Account
  18. 18. Opportunities with Approval Status Set to "Pending"
  19. 19. Accounts Not Modified in the Last 60 Days
  20. 20. Closed Won Opportunities with a Close Date in the Last Quarter
  21. 21. Account Names with the Total Number of Associated Contacts
  22. 22. High-Priority Opportunities with Amount Greater Than $50,000
  23. 23. Accounts That Have at Least One Prospecting Opportunity
  24. 24. SOQL Child-to-Parent: Opportunity with Account Owner (2-Level)
  25. 25. SOQL Child-to-Parent: Filter Contacts by Account Type
  26. 26. SOQL Child-to-Parent: Case with Contact and Account Names
  27. 27. SOQL Child-to-Parent: Filter Opportunities by Account Revenue
  28. 28. SOQL Child-to-Parent: Contact Directory with Multiple Account Fields
  29. 29. SOQL Child-to-Parent: Contact with Account Owner (2-Level)
  30. 30. SOQL Parent-to-Child: Account with Open Cases
  31. 31. SOQL Parent-to-Child: Account with Minimal Contact Subquery
  32. 32. SOQL Parent-to-Child: Account with Contacts Filtered by Email
  33. 33. SOQL Parent-to-Child: Account with Opportunities Ordered by Close Date
  34. 34. SOQL Parent-to-Child: Account with Contacts Sorted NULLS LAST
  35. 35. SOQL Parent-to-Child: Account with Two Subqueries (Contacts + Opportunities)
  36. 36. SOQL Parent-to-Child: Account with Cases and Case Comments
  37. 37. SOQL Combined: Child-to-Parent + Parent-to-Child
  38. 38. SOQL: Aggregate — Accounts with 5 or More Contacts
  39. 39. SOQL: Semi-Join and Anti-Join Subqueries
  40. 40. SOQL: Dynamic SOQL with String.escapeSingleQuotes
  41. 41. SOQL: Aggregate with CALENDAR_MONTH and FISCAL_YEAR Date Functions
  42. 42. SOQL: Sum of All Opportunity Amounts
  43. 43. SOQL: Average Amount of All Opportunities
  44. 44. SOQL: Maximum Opportunity Amount
  45. 45. SOQL: Minimum Opportunity Amount
  46. 46. SOQL: Count Opportunities Grouped by Stage
  47. 47. SOQL: Filter Groups With HAVING Clause
  48. 48. SOQL: Child-to-Parent Relationship — Contact to Account Name
  49. 49. SOQL: Child-to-Parent Relationship — Opportunity to Account Name
  50. 50. SOQL: Multi-Level Child-to-Parent — Opportunity Owner Profile
  51. 51. SOQL: Parent-to-Child Subquery — Account With Contacts
  52. 52. SOQL: Parent-to-Child Subquery — Account With Opportunities
  53. 53. SOQL: Parent-to-Child Subquery With WHERE Filter
  54. 54. SOQL: Semi-Join — Accounts That Have Opportunities
  55. 55. SOQL: Anti-Join — Accounts Without Any Opportunities
  56. 56. SOQL: Total Opportunity Amount Grouped by Stage
  57. 57. SOQL: Dynamic SOQL Using Database.query()
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

  1. Compute average: SELECT AVG(Amount) avgAmt FROM Opportunity
  2. Collect AccountIds: SELECT AccountId FROM Opportunity WHERE Amount > :avgAmt
  3. 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

Halfway there — solve them live

Every problem above runs in a real in-browser Apex editor with instant pass/fail feedback and best-practice linting. No Salesforce org needed.

Create Free Account →
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.

Practice All 57 Problems Free

Write and run real Apex code right in your browser — instant pass/fail feedback, best-practice linting, and governor limit monitoring. No Salesforce org needed.

Create Free Account → Explore All Problems
More Practice Topics
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