🏠 Home 🔒 Record Sharing ⚙ Apex Triggers 🔍 SOQL 💻 LWC 🔗 Integration 🤖 Flows & Automation 🤖 Agentforce & AI 🎈 Agentforce Course — Free ☁ Data Cloud 🎓 DC Course — Free 🚀 DevOps Course — Free 💵 CPQ 🎯 100 Scenario Questions 🏆 150 Advanced Questions 📧 Marketing Cloud 🎤 Mock Interview Community 🏗️ Company Wise 👥 About Us Start Learning Free →

Top 40 Salesforce SOQL Interview Questions and Answers 2026 — Aggregate Queries, Relationships, Governor Limits & SOSL Explained with Apex Code

📅  Soql
Salesforce SOQL Interview Questions — Queries, Aggregates & Relationships Explained
🗄️ SOQL

Salesforce SOQL Interview Questions — Queries, Aggregates & Relationships Explained

SELECT, GROUP BY, HAVING, Date Literals, Sub-Queries & Relationship Traversal — Beginner to Intermediate

Beginner Intermediate 13 Questions
Q
Question 01 · 🟢 Basic
Retrieve all Account names and their corresponding Industry from Salesforce.
✅ Answer
Use a basic SELECT statement. SOQL does NOT support SELECT * — field names must always be explicitly specified! 📋
📌 Query
SELECT Name, Industry FROM Account ORDER BY Name ASC
📋 Breaking It Down
ClausePurpose
SELECT Name, IndustryFetch only these two fields from the Account object
FROM AccountTarget the standard Account sObject
ORDER BY Name ASCSort results alphabetically by Account name
⚠️ No SELECT * in SOQL. Writing SELECT * FROM Account will throw an error in Salesforce. Always use field API names explicitly.
💻 In Apex
List<Account> accounts = [ SELECT Name, Industry FROM Account ORDER BY Name ASC ]; for (Account acc : accounts) { System.debug(acc.Name + ' | ' + acc.Industry); }
🎤 One-Line Answer for Interview
"I use SOQL's SELECT statement to query the Name and Industry fields from the Account object — SOQL doesn't support SELECT *, so field names must always be explicitly specified."
Q
Question 02 · 🟠 Intermediate
Find the total number of Opportunities for each Account.
✅ Answer
Use COUNT(Id) with GROUP BY. In Apex, aggregate queries return AggregateResult — not a standard sObject list! 📊
📌 Query
SELECT AccountId, Account.Name, COUNT(Id) total FROM Opportunity GROUP BY AccountId, Account.Name ORDER BY COUNT(Id) DESC
📋 Breaking It Down
ClausePurpose
COUNT(Id)Counts total Opportunities for each Account
GROUP BY AccountId, Account.NameRequired when using any aggregate function
ORDER BY COUNT(Id) DESCShows Account with highest Opportunities first
⚠️ Golden Rule: Every non-aggregate field in SELECT must also be in GROUP BY. Use HAVING (not WHERE) to filter on aggregated results — e.g. HAVING COUNT(Id) > 5
💻 In Apex
List<AggregateResult> results = [ SELECT AccountId, Account.Name, COUNT(Id) total FROM Opportunity GROUP BY AccountId, Account.Name ]; for (AggregateResult ar : results) { System.debug(ar.get('Name') + ' → ' + ar.get('total')); }
🎤 One-Line Answer for Interview
"I use COUNT(Id) with GROUP BY AccountId on the Opportunity object to get total Opportunities per Account — and since it's an aggregate query, the Apex return type is AggregateResult, not a standard sObject list."
Q
Question 03 · 🟠 Intermediate
Retrieve the names of Contacts associated with an Opportunity named "XYZ Opportunity."
✅ Answer
Contact and Opportunity are NOT directly related — they are connected through the standard junction object OpportunityContactRole! 🔗
🔗 Relationship Chain: Opportunity ──── OpportunityContactRole ──── Contact
📌 Approach 1 — Query from OCR (Most Common)
SELECT Contact.Name, Contact.Email, Role FROM OpportunityContactRole WHERE Opportunity.Name = 'XYZ Opportunity'
📌 Approach 2 — Unique Contacts Only (No Duplicates)
SELECT Name, Email FROM Contact WHERE Id IN ( SELECT ContactId FROM OpportunityContactRole WHERE Opportunity.Name = 'XYZ Opportunity' )
🔑 Approach 1 vs Approach 2
  • 💡Approach 1 returns one row per OCR record — same Contact appears multiple times if linked to multiple Opportunities
  • 💡Approach 2 returns unique Contacts only by querying the Contact object directly via sub-query
🎤 One-Line Answer for Interview
"Contact and Opportunity are not directly related — they are linked through the standard junction object OpportunityContactRole. So I query OpportunityContactRole and traverse the relationship to Contact.Name using dot notation."
Q
Question 04 · 🟢 Basic
List all Closed Won Opportunities along with their amounts.
✅ Answer
Filter using WHERE StageName = 'Closed Won'. StageName is a picklist field and the value is case-sensitive! 🎯
📌 Query
SELECT Name, Amount, StageName, CloseDate, Account.Name FROM Opportunity WHERE StageName = 'Closed Won' ORDER BY Amount DESC
⚠️ 'closed won' or 'Closed won' will return ZERO records. Must be exactly 'Closed Won'.
📋 Opportunity Stage Reference
StageNameIsClosedIsWonMeaning
ProspectingfalsefalseEarly stage lead
Proposal/Price QuotefalsefalseQuote shared
Closed Won✅ true✅ trueDeal won
Closed Lost✅ true❌ falseDeal lost
💡 IsClosed = true returns BOTH Closed Won AND Closed Lost. Use StageName = 'Closed Won' specifically for only won deals.
🎤 One-Line Answer for Interview
"I filter the Opportunity object using WHERE StageName = 'Closed Won' — StageName is a picklist field and the value is case-sensitive. IsClosed = true returns both Closed Won and Closed Lost, so for only won deals, StageName filter is the right approach."
Q
Question 05 · 🟢 Basic
Identify the five most recently created Leads.
✅ Answer
Use ORDER BY CreatedDate DESC + LIMIT 5. Forgetting DESC returns the OLDEST 5 leads — ASC is the default! 📅
📌 Query
SELECT Name, Email, Phone, Company, LeadSource, Status, CreatedDate FROM Lead ORDER BY CreatedDate DESC LIMIT 5
📋 LIMIT — Key Rules
ScenarioBehaviour
LIMIT 1Exactly 1 record — used when you expect a unique result
LIMIT 200Default max records shown in Developer Console
LIMIT 50000Maximum records a single SOQL can return in Apex
💡 CreatedDate vs LastModifiedDate: CreatedDate is set ONCE when record is created — never changes. LastModifiedDate updates every time the record is edited.
⚠️ Always pair LIMIT with ORDER BY for predictable results — without ORDER BY, LIMIT picks records in arbitrary order.
🎤 One-Line Answer for Interview
"I use ORDER BY CreatedDate DESC to sort Leads from newest to oldest and LIMIT 5 to restrict the output — DESC is critical here because the default SOQL sort order is ascending, which would return the oldest records instead of the newest."
Q
Question 06 · 🟠 Intermediate
Retrieve Account names and their associated Opportunities where the Opportunity Amount is greater than $50,000.
✅ Answer
Two approaches — Child to Parent (dot notation) or Parent to Child (sub-query). Both valid — choose based on what data you need as the primary result! 🔗
📌 Approach 1 — Child to Parent (Most Common)
SELECT Account.Name, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Amount > 50000 ORDER BY Amount DESC
📌 Approach 2 — Parent to Child (Sub-query)
SELECT Name, (SELECT Name, Amount, StageName FROM Opportunities WHERE Amount > 50000) FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity WHERE Amount > 50000)
⚠️ Opportunity is the object name — Opportunities (plural) is the child relationship name used in sub-queries. Wrong name = error.
📋 Child to Parent vs Parent to Child
TypeSyntaxUse When
Child to ParentAccount.Name (dot notation)Need Opp data + parent Account name
Parent to Child(SELECT ... FROM Opportunities)Need Account + all its child Opps
🎤 One-Line Answer for Interview
"I query from Opportunity and use dot notation Account.Name to traverse up to the parent Account — Child-to-Parent relationship. Amount > 50000 uses no quotes since Amount is a Currency field. The Parent-to-Child sub-query uses the relationship name Opportunities in plural."
Q
Question 07 · 🟠 Intermediate
Find the average amount of all Closed Won Opportunities.
✅ Answer
Use AVG() aggregate function. Since we calculate a single value across all records with no grouping needed, no GROUP BY is required! 📊
📌 Query
SELECT AVG(Amount) avgAmount FROM Opportunity WHERE StageName = 'Closed Won'
📋 All SOQL Aggregate Functions
FunctionPurposeReturns
COUNT()Total rows (including nulls)Integer
COUNT(field)Count of non-null valuesInteger
SUM(field)Total sum of numeric fieldDecimal
AVG(field)Average of numeric fieldDecimal
MIN(field)Minimum valueSame as field type
MAX(field)Maximum valueSame as field type
💡 All aggregate functions ignore NULL values except COUNT(). COUNT() counts all rows including those with null fields — a very common interview distinction!
💻 In Apex
List<AggregateResult> result = [ SELECT AVG(Amount) avgAmount FROM Opportunity WHERE StageName = 'Closed Won' ]; if (!result.isEmpty()) { Decimal avg = (Decimal) result[0].get('avgAmount'); System.debug('Average: ' + avg); }
🎤 One-Line Answer for Interview
"I use the AVG() aggregate function on the Amount field with WHERE StageName = 'Closed Won' — since it's a single aggregated value with no grouping, no GROUP BY is needed. In Apex, return type is AggregateResult and I cast the result as Decimal using ar.get('aliasName')."
Q
Question 08 · 🟠 Intermediate
List the Opportunities that are due to close in the next 7 days.
✅ Answer
Use SOQL Date Literal: NEXT_N_DAYS:7. Date Literals are preferred over hardcoded dates — they automatically adjust relative to today! 📅
📌 Query
SELECT Name, Amount, StageName, CloseDate, Account.Name FROM Opportunity WHERE CloseDate = NEXT_N_DAYS:7 AND IsClosed = false ORDER BY CloseDate ASC
📋 Most Important SOQL Date Literals
Date LiteralMeaning
TODAYOnly today's date
YESTERDAY / TOMORROWYesterday or tomorrow's date
THIS_WEEK / THIS_MONTHCurrent week (Sun–Sat) or calendar month
THIS_FISCAL_YEARCurrent fiscal year as configured in org
LAST_N_DAYS:nPast n days from today (excludes today)
NEXT_N_DAYS:nNext n days from tomorrow (excludes today)
LAST_N_MONTHS:nPast n months
⚠️ NEXT_N_DAYS:7 does NOT include today. To include today: WHERE CloseDate >= TODAY AND CloseDate <= NEXT_N_DAYS:7
💡 Bonus — Overdue open deals: WHERE CloseDate < TODAY AND IsClosed = false — very useful for sales managers to identify deals that passed their close date!
🎤 One-Line Answer for Interview
"I use the SOQL Date Literal NEXT_N_DAYS:7 on CloseDate to dynamically filter Opportunities closing in the next 7 days — Date Literals are preferred over hardcoded dates because they automatically adjust relative to today. I also add IsClosed = false to exclude already closed deals."
Q
Question 09 · 🟠 Intermediate
Retrieve the names and email addresses of all Contacts associated with Opportunities that are in the "Proposal" stage.
✅ Answer
This is a 3-object join — Opportunity + OpportunityContactRole + Contact. Traverse from OCR up to both objects using dot notation simultaneously! 🔗
📌 Approach 1 — With Opportunity Context
SELECT Contact.Name, Contact.Email, Opportunity.Name, Role FROM OpportunityContactRole WHERE Opportunity.StageName = 'Proposal/Price Quote' ORDER BY Contact.Name ASC
📌 Approach 2 — Unique Contacts Only
SELECT Name, Email FROM Contact WHERE Id IN ( SELECT ContactId FROM OpportunityContactRole WHERE Opportunity.StageName = 'Proposal/Price Quote' ) ORDER BY Name ASC
⚠️ 'Proposal' alone is NOT a standard Salesforce stage name. The correct standard value is 'Proposal/Price Quote'. Always verify exact picklist values in Setup → Object Manager → Opportunity → StageName.
💡 SOQL supports up to 5 levels of parent relationship traversal via dot notation — e.g. Opportunity.Account.Owner.Profile.Name
🎤 One-Line Answer for Interview
"Contact and Opportunity are linked through OpportunityContactRole — I query from OCR and use dot notation to traverse to Contact.Name, Contact.Email, and Opportunity.StageName simultaneously. For unique Contacts without duplicates, I use a sub-query: WHERE Id IN (SELECT ContactId FROM OpportunityContactRole WHERE Opportunity.StageName = 'Proposal/Price Quote')."
Q
Question 10 · 🟠 Intermediate
Find the Account with the maximum number of Opportunities.
✅ Answer
There is no direct MAX(COUNT()) in SOQL. Use the pattern: COUNT → GROUP BY → ORDER BY DESC → LIMIT 1! 🏆
📌 Query
SELECT AccountId, Account.Name, COUNT(Id) totalOpps FROM Opportunity GROUP BY AccountId, Account.Name ORDER BY COUNT(Id) DESC LIMIT 1
📋 The 4-Step Pattern — Maximum in a Group
StepClausePurpose
1COUNT(Id)Count Opportunities per group
2GROUP BY AccountId, Account.NameDefine the grouping
3ORDER BY COUNT(Id) DESCRank highest count first
4LIMIT 1Pick only the top result
⚠️ SOQL does NOT support MAX(COUNT(Id)) — throws error. Always use the ORDER BY DESC + LIMIT 1 pattern.
💡 Handling Ties: If two Accounts have same count, LIMIT 1 returns only one. Use HAVING COUNT(Id) >= 18 to get all tied Accounts.
🎤 One-Line Answer for Interview
"SOQL does not support MAX(COUNT()) directly — so I use COUNT(Id) with GROUP BY AccountId, sort using ORDER BY COUNT(Id) DESC, and pick the top result with LIMIT 1. To handle tie scenarios, I replace LIMIT 1 with a HAVING COUNT(Id) >= n filter."
Q
Question 11 · 🟠 Intermediate
Find the Accounts that have exactly 2 Contacts.
✅ Answer
Use HAVING clause — you CANNOT use WHERE COUNT(Id) = 2, that throws an error. HAVING is the only way to filter on aggregate functions! 🎯
📌 Query
SELECT AccountId, Account.Name, COUNT(Id) totalContacts FROM Contact WHERE AccountId != null GROUP BY AccountId, Account.Name HAVING COUNT(Id) = 2 ORDER BY Account.Name ASC
📋 WHERE vs HAVING — The Golden Rule
ClauseRuns WhenFilters OnExample
WHEREBefore groupingIndividual record fieldsWHERE AccountId != null
HAVINGAfter groupingAggregated results onlyHAVING COUNT(Id) = 2
💡 Both can be used together in the same query — WHERE filters individual records first, then HAVING filters the aggregated groups after.
🎤 One-Line Answer for Interview
"I query from Contact, use COUNT(Id) with GROUP BY AccountId and Account.Name, and apply HAVING COUNT(Id) = 2 to filter only Accounts with exactly 2 Contacts — HAVING is used because WHERE cannot filter on aggregated results, it only works on individual record fields before grouping."
Q
Question 12 · 🟠 Intermediate
Write a SOQL query to fetch the Account record with maximum Opportunities.
✅ Answer
Since ORDER BY and LIMIT are NOT allowed inside SOQL sub-queries, a 2-Step Apex approach is required to fetch the complete Account record! 🔗
📌 Step 1 — Find AccountId with Max Opportunities
SELECT AccountId, COUNT(Id) totalOpps FROM Opportunity GROUP BY AccountId ORDER BY COUNT(Id) DESC LIMIT 1
📌 Step 2 — Fetch Full Account Record
SELECT Id, Name, Industry, Phone, BillingCity, Website, Type, AnnualRevenue, Owner.Name FROM Account WHERE Id = :topAccountId // bind variable from Step 1
💻 Full Apex Implementation
List<AggregateResult> agg = [ SELECT AccountId, COUNT(Id) totalOpps FROM Opportunity GROUP BY AccountId ORDER BY COUNT(Id) DESC LIMIT 1 ]; if (!agg.isEmpty()) { Id topId = (Id) agg[0].get('AccountId'); Account acc = [ SELECT Id, Name, Industry, Phone, BillingCity, Owner.Name FROM Account WHERE Id = :topId LIMIT 1 ]; System.debug(acc.Name + ' — ' + agg[0].get('totalOpps') + ' Opportunities'); }
⚠️ ORDER BY and LIMIT are NOT allowed inside SOQL sub-queries. WHERE Id IN (SELECT AccountId ... ORDER BY COUNT(Id) DESC LIMIT 1) will throw a compile error. Always use the 2-step Apex approach.
🎤 One-Line Answer for Interview
"Since SOQL doesn't allow ORDER BY and LIMIT inside sub-queries, I use a 2-step Apex approach — first an aggregate query on Opportunity with COUNT(Id), GROUP BY AccountId, ORDER BY COUNT(Id) DESC, LIMIT 1 to get the top AccountId, then a second query on Account using that Id to fetch the complete record."
Q
Question 13 · 🟢 Basic
Write a SOQL query to fetch the latest 10 records of the Account.
✅ Answer
Use ORDER BY CreatedDate DESC + LIMIT 10. For pagination beyond 2000 records, switch from OFFSET to Keyset Pagination! 📋
📌 Query — Latest 10 by Creation Date
SELECT Id, Name, Industry, Phone, BillingCity, BillingCountry, Type, CreatedDate, CreatedBy.Name, Owner.Name FROM Account ORDER BY CreatedDate DESC LIMIT 10
📌 Bonus — Pagination using OFFSET
// Page 1 → Records 1–10 SELECT Id, Name, CreatedDate FROM Account ORDER BY CreatedDate DESC LIMIT 10 OFFSET 0 // Page 2 → Records 11–20 SELECT Id, Name, CreatedDate FROM Account ORDER BY CreatedDate DESC LIMIT 10 OFFSET 10
📋 Standard Audit Fields — Quick Reference
FieldPurposeChangeable?
CreatedDateWhen record was first created❌ Never
CreatedByIdUser who created the record❌ Never
LastModifiedDateWhen record was last edited✅ Auto-updated
LastModifiedByIdUser who last edited the record✅ Auto-updated
SystemModstampLast modified by system or user (includes automation)✅ Auto-updated
⚠️ OFFSET has a hard limit of 2000 records. For datasets beyond 2000, use Keyset Pagination: WHERE CreatedDate < :lastCreatedDate ORDER BY CreatedDate DESC LIMIT 10
🎤 One-Line Answer for Interview
"I use ORDER BY CreatedDate DESC to sort Account records newest to oldest and LIMIT 10 to restrict output — DESC is critical because default SOQL sort is ascending. For pagination beyond 2000 records, OFFSET has a hard limit so I switch to keyset pagination using the last CreatedDate value as a cursor."