SOQL & SOSL Deep Dive
π Concept
SOQL (Salesforce Object Query Language) and SOSL (Salesforce Object Search Language) are the two ways to retrieve data in Apex. Understanding their differences, performance characteristics, and optimization techniques is critical for every Salesforce developer.
SOQL vs SOSL:
SOQL:
- Queries ONE object at a time (with relationships)
- Structured query: SELECT fields FROM object WHERE conditions
- Returns exact matches
- Can traverse relationships (parent-to-child, child-to-parent)
- 100 queries per synchronous transaction / 200 async
- Up to 50,000 rows returned
SOSL:
- Searches ACROSS MULTIPLE objects simultaneously
- Text-based search: FIND 'search term' IN ALL FIELDS
- Returns approximate matches (search index)
- Searches across all text-searchable fields
- 20 searches per transaction
- Up to 2,000 rows returned
SOQL essentials:
- WHERE clauses β filter records (=, !=, >, <, IN, LIKE, NOT IN)
- ORDER BY β sort results (ASC/DESC)
- LIMIT / OFFSET β pagination
- GROUP BY / HAVING β aggregate queries (COUNT, SUM, AVG, MIN, MAX)
- Relationship queries β traverse parent (dot notation) and child (subquery)
- Date literals β TODAY, LAST_N_DAYS:30, THIS_QUARTER, etc.
- Polymorphic queries β TYPEOF for querying relationships that can reference multiple objects
Query optimization: Salesforce maintains custom indexes on certain fields. Your WHERE clause must be selective β meaning it uses an indexed field that filters out at least 90% of records. Non-selective queries on large objects (>100K records) will fail.
Indexed fields (automatic):
- Id, Name, OwnerId, CreatedDate, SystemModstamp
- Lookup and Master-Detail relationship fields
- Custom fields marked as "External ID" or "Unique"
SOQL anti-patterns to avoid:
- Queries inside loops (governor limit violation)
- SELECT * equivalent (SELECT all fields β wastes heap and query time)
- Non-selective queries on large objects
- Not using bind variables (SOQL injection risk)
π» Code Example
1// SOQL & SOSL Mastery23public class QueryExamples {45 // 1. Basic SOQL with filters and ordering6 public static List<Account> getActiveAccounts() {7 return [8 SELECT Id, Name, Industry, AnnualRevenue, CreatedDate9 FROM Account10 WHERE Industry = 'Technology'11 AND AnnualRevenue > 100000012 AND CreatedDate = THIS_YEAR13 ORDER BY AnnualRevenue DESC14 LIMIT 5015 ];16 }1718 // 2. Relationship queries β Parent-to-Child (subquery)19 public static List<Account> getAccountsWithContacts() {20 return [21 SELECT Name, Industry,22 (SELECT FirstName, LastName, Email, Title23 FROM Contacts24 WHERE Email != null25 ORDER BY LastName ASC26 LIMIT 5)27 FROM Account28 WHERE Industry = 'Technology'29 LIMIT 1030 ];31 // Access child records:32 // for (Account acc : results) {33 // for (Contact c : acc.Contacts) { ... }34 // }35 }3637 // 3. Relationship queries β Child-to-Parent (dot notation)38 public static List<Contact> getContactsWithAccountInfo() {39 return [40 SELECT FirstName, LastName,41 Account.Name, // Parent field42 Account.Industry, // Navigate up43 Account.Owner.Name // Multiple levels up44 FROM Contact45 WHERE Account.Industry = 'Technology'46 ];47 }4849 // 4. Aggregate queries50 public static void aggregateExamples() {51 // COUNT52 Integer totalAccounts = [SELECT COUNT() FROM Account];5354 // GROUP BY with aggregates55 List<AggregateResult> byIndustry = [56 SELECT Industry, COUNT(Id) cnt, SUM(AnnualRevenue) totalRev57 FROM Account58 WHERE Industry != null59 GROUP BY Industry60 HAVING COUNT(Id) > 561 ORDER BY COUNT(Id) DESC62 ];6364 for (AggregateResult ar : byIndustry) {65 String industry = (String) ar.get('Industry');66 Integer count = (Integer) ar.get('cnt');67 Decimal revenue = (Decimal) ar.get('totalRev');68 System.debug(industry + ': ' + count + ' accounts, $' + revenue);69 }70 }7172 // 5. Dynamic SOQL (useful for configurable queries)73 public static List<SObject> dynamicQuery(74 String objectName,75 List<String> fields,76 String whereClause,77 Integer limitCount78 ) {79 String query = 'SELECT ' + String.join(fields, ', ') +80 ' FROM ' + String.escapeSingleQuotes(objectName);8182 if (String.isNotBlank(whereClause)) {83 query += ' WHERE ' + whereClause;84 }8586 query += ' LIMIT ' + limitCount;8788 return Database.query(query);89 }9091 // 6. SOSL β Search across multiple objects92 public static void searchExamples() {93 // SOSL searches across objects94 List<List<SObject>> results = [95 FIND 'Acme*' IN ALL FIELDS96 RETURNING97 Account(Id, Name, Industry WHERE Industry = 'Technology'),98 Contact(Id, FirstName, LastName, Email),99 Opportunity(Id, Name, Amount, StageName)100 LIMIT 20101 ];102103 List<Account> accounts = (List<Account>) results[0];104 List<Contact> contacts = (List<Contact>) results[1];105 List<Opportunity> opps = (List<Opportunity>) results[2];106107 System.debug('Found ' + accounts.size() + ' accounts');108 System.debug('Found ' + contacts.size() + ' contacts');109 }110111 // 7. Date literals and filtering112 public static List<Opportunity> getRecentOpportunities() {113 return [114 SELECT Name, StageName, CloseDate, Amount115 FROM Opportunity116 WHERE CloseDate = THIS_QUARTER117 AND StageName NOT IN ('Closed Won', 'Closed Lost')118 AND Amount > 50000119 ORDER BY CloseDate ASC120 ];121 }122123 // 8. Bind variables (prevent SOQL injection)124 public static List<Account> safeQuery(String searchName) {125 // GOOD β bind variable (safe from injection)126 String safeName = '%' + searchName + '%';127 return [SELECT Id, Name FROM Account WHERE Name LIKE :safeName];128129 // BAD β string concatenation (SOQL injection vulnerable!)130 // String query = 'SELECT Id FROM Account WHERE Name LIKE '%'131 // + searchName + '%'';132 // return Database.query(query);133 }134135 // 9. Query performance β using selective filters136 public static List<Account> performantQuery() {137 // SELECTIVE β uses indexed field (Id, Name, lookups, External IDs)138 return [139 SELECT Id, Name FROM Account140 WHERE Name = 'Acme Corporation' // Name is indexed141 LIMIT 1142 ];143144 // NON-SELECTIVE β scans all records (fails on large objects)145 // SELECT Id FROM Account WHERE Description LIKE '%enterprise%'146 // Description is NOT indexed β full table scan!147 }148149 // 10. FOR UPDATE β record locking150 public static void lockRecords() {151 List<Account> accounts = [152 SELECT Id, Name, AnnualRevenue153 FROM Account154 WHERE Id = :accountId155 FOR UPDATE // Locks the record to prevent concurrent modification156 ];157 // Other transactions trying to update this record will wait158 accounts[0].AnnualRevenue += 50000;159 update accounts;160 }161}
ποΈ Practice Exercise
SOQL/SOSL Mastery Exercises:
- Write a SOQL query that returns Accounts with their Opportunities and Contacts in a single query
- Write an aggregate query that shows total Opportunity Amount by Stage, grouped by Account Industry
- Use SOSL to search for 'John' across Account, Contact, and Lead objects simultaneously
- Write a dynamic SOQL builder that accepts a Map of fieldβvalue filters and constructs a WHERE clause
- Query all Contacts where the Account was created in the last 30 days (child-to-parent with date literal)
- Write a SOQL query that uses OFFSET for pagination (page 3, 25 records per page)
- Use FOR UPDATE in a query and explain when locking is necessary
- Write a query that finds all Accounts with NO related Contacts (anti-join pattern)
- Build a reusable query class that handles pagination, sorting, and filtering dynamically
- Use the Query Plan tool in Developer Console to analyze query selectivity for 5 different queries
β οΈ Common Mistakes
Writing SOQL inside loops β this is the #1 cause of governor limit failures. Always query once, build a Map, then iterate
Not using bind variables in dynamic SOQL β String.escapeSingleQuotes helps but bind variables are the proper protection against SOQL injection
Selecting all fields (SELECT *) β Apex doesn't support *, but developers query too many fields, wasting heap. Query only what you need
Using LIKE with leading wildcards on large objects β '%searchterm%' prevents index usage and causes non-selective query errors
Not understanding SOQL vs SOSL β SOQL is for structured queries on known objects; SOSL is for text search across multiple objects
πΌ Interview Questions
π€ Mock Interview
Mock interview is powered by AI for SOQL & SOSL Deep Dive. Login to unlock this feature.