SFX Support Header
logo

SOQL & SOSL Fundamentals - Mastering Salesforce Development

Unlock the power of data retrieval in Salesforce! This module covers the fundamentals of SOQL (Salesforce Object Query Language) and SOSL (Salesforce Object Search Language), essential tools for developers to interact with the Salesforce database.

Need hands-on training?

1. Introduction to SOQL & SOSL

In Salesforce development, interacting with data stored in your organization's database is fundamental. Unlike traditional SQL databases, Salesforce uses its own query languages tailored for its unique object model:

  • SOQL (Salesforce Object Query Language): Used to query a single sObject or multiple sObjects that are related to one another. It is similar to the SELECT statement in SQL.
  • SOSL (Salesforce Object Search Language): Used to perform text-based searches across multiple sObjects, returning results that match a search term. It is similar to search engine queries.

Why are SOQL & SOSL essential?

  • Data Retrieval: Get the exact data you need from Salesforce objects.
  • Automation: Power your Apex triggers, classes, Visualforce pages, and Lightning Components with dynamic data.
  • Business Logic: Implement complex business rules that depend on querying existing data.
  • Reporting & Dashboards: Although not directly, understanding SOQL helps in comprehending how reports and list views fetch data.

Key Differences:

  • SOQL: Best for retrieving specific data from a known object (or related objects) when you know the object structure and specific fields. Similar to looking up an address in a phone book by name.
  • SOSL: Best for searching text across multiple objects and fields when you don't know which object contains the data, but you have a search term. Similar to searching for a keyword on the internet.

Both query languages are subject to Salesforce's governor limits to ensure efficient use of shared resources.

2. SOQL Basics

SOQL queries are used in Apex, Visualforce controllers, Lightning components, and the Developer Console. They retrieve a list of sObject records.

Basic Syntax:

SELECT FieldName1, FieldName2 FROM ObjectApiName [WHERE Conditions] [ORDER BY FieldName [ASC|DESC]] [LIMIT Number] [OFFSET Number]

Let's break down the common clauses:

  • SELECT FieldName1, FieldName2: Specifies the fields you want to retrieve. You can select standard fields, custom fields (ending with `__c`), and even relationship fields (e.g., `Account.Name`).
  • FROM ObjectApiName: Specifies the sObject (e.g., `Account`, `Contact`, `MyCustomObject__c`) you are querying.
  • WHERE Conditions: Filters the records based on criteria. You can use operators like `=`, `!=`, `<`, `>`, `<=`, `>=`, `LIKE`, `IN`, `NOT IN`, `AND`, `OR`.
  • ORDER BY FieldName [ASC|DESC]: Sorts the results based on one or more fields. `ASC` (ascending) is default, `DESC` (descending) sorts in reverse.
  • LIMIT Number: Restricts the number of rows returned. Useful for performance.
  • OFFSET Number: Skips a specified number of rows from the beginning of the result set. Used for pagination.

Examples:

Retrieve Account Names and Industries:

SELECT Name, Industry FROM Account

Retrieve Contacts from a specific city:

SELECT Id, FirstName, LastName, Email FROM Contact WHERE MailingCity = 'San Francisco'

Retrieve Accounts created in the last 30 days, sorted by name, limited to 10:

SELECT Id, Name, CreatedDate FROM Account WHERE CreatedDate = LAST_N_DAYS:30 ORDER BY Name ASC LIMIT 10

Retrieve Opportunities in a specific Stage and Amount range:

SELECT Name, StageName, Amount FROM Opportunity WHERE StageName = 'Closed Won' AND Amount >= 10000 ORDER BY Amount DESC

Using SOQL in Apex:

In Apex, you embed SOQL queries directly within square brackets `[]`. The query returns a `List` of sObjects.

List<Account> topAccounts = [SELECT Id, Name, AnnualRevenue FROM Account ORDER BY AnnualRevenue DESC LIMIT 5];

for (Account acc : topAccounts) {
    System.debug('Account Name: ' + acc.Name + ', Revenue: ' + acc.AnnualRevenue);
}

// Single record retrieval (if you expect only one record)
Account singleAccount = [SELECT Id, Name FROM Account WHERE Name = 'Acme Corp' LIMIT 1];
System.debug('Found Account: ' + singleAccount.Name);

// Handling no results for single query (using try-catch or checking size)
try {
    Account nonExistentAccount = [SELECT Id FROM Account WHERE Name = 'NonExistent' LIMIT 1];
} catch (QueryException e) {
    System.debug('No account found with that name: ' + e.getMessage());
}

3. SOQL Relationship Queries

Salesforce's relational data model allows you to query related records directly within SOQL. This is a powerful feature that reduces the number of queries needed, helping to stay within governor limits.

Parent-to-Child Relationships (Nested Queries):

You can query child records from a parent object. This is done by writing a subquery in the SELECT statement. The relationship name for the child object is typically the plural of the child object's name (e.g., `Contacts` for `Contact` records related to `Account`).

SELECT Id, Name, (SELECT Id, FirstName, LastName FROM Contacts) FROM Account

In Apex, the child records are accessed via the relationship name, which is a list on the parent sObject.

List<Account> accountsWithContacts = [SELECT Id, Name, (SELECT Id, FirstName, LastName, Email FROM Contacts) FROM Account WHERE Name LIKE 'ABC%'];

for (Account acc : accountsWithContacts) {
    System.debug('Account: ' + acc.Name);
    if (acc.Contacts != null) {
        for (Contact con : acc.Contacts) {
            System.debug('  Contact: ' + con.FirstName + ' ' + con.LastName + ' (Email: ' + con.Email + ')');
        }
    }
}

Child-to-Parent Relationships:

You can query fields from a parent object from a child object. This is done by traversing the relationship using dot notation. The relationship name is typically the lookup/master-detail field name without `__c`, followed by `__r` if it's a custom relationship (e.g., `Account` for a standard lookup field on `Contact`, or `MyCustomLookup__r` for a custom lookup).

SELECT Id, Name, Account.Name, Account.Industry FROM Contact WHERE Account.Industry = 'Technology'

In Apex:

List<Contact> techContacts = [SELECT Id, FirstName, LastName, Account.Name, Account.Industry, Account.AnnualRevenue FROM Contact WHERE Account.Industry = 'Technology'];

for (Contact con : techContacts) {
    System.debug('Contact: ' + con.FirstName + ' ' + con.LastName);
    if (con.Account != null) { // Always null-check parent relationships
        System.debug('  Related Account: ' + con.Account.Name + ' (Industry: ' + con.Account.Industry + ')');
    }
}

4. SOQL Aggregate Functions & Grouping

SOQL supports aggregate functions to perform calculations on a set of records and return a single value. These are often used with the `GROUP BY` clause to group results by a specific field.

Common Aggregate Functions:

  • COUNT(): Returns the number of rows.
  • COUNT(FieldName): Returns the number of rows where `FieldName` is not null.
  • COUNT(DISTINCT FieldName): Returns the number of unique non-null values of a field.
  • SUM(FieldName): Calculates the sum of a numeric field.
  • AVG(FieldName): Calculates the average of a numeric field.
  • MIN(FieldName): Returns the minimum value of a field.
  • MAX(FieldName): Returns the maximum value of a field.

Syntax with GROUP BY:

SELECT AggregateFunction(FieldName), FieldName2 FROM ObjectApiName [WHERE Conditions] GROUP BY FieldName2 [HAVING AggregateConditions]

The HAVING clause is used to filter results based on aggregate functions, similar to `WHERE` for non-aggregate fields.

Examples:

Count of all Accounts:

SELECT COUNT() FROM Account
Integer accountCount = [SELECT COUNT() FROM Account];
System.debug('Total Accounts: ' + accountCount);

Sum of Annual Revenue by Industry:

SELECT Industry, SUM(AnnualRevenue) FROM Account GROUP BY Industry
// When using aggregate functions, the result is a List
List<AggregateResult> results = [SELECT Industry, SUM(AnnualRevenue) totalRevenue FROM Account GROUP BY Industry];

for (AggregateResult ar : results) {
    System.debug('Industry: ' + ar.get('Industry') + ', Total Revenue: ' + ar.get('totalRevenue'));
}

Count of Opportunities by Stage, only for stages with more than 5 opportunities:

SELECT StageName, COUNT(Id) FROM Opportunity GROUP BY StageName HAVING COUNT(Id) > 5

5. SOQL For Loops

SOQL For Loops (also known as "for-each loops" with SOQL queries) are a highly efficient way to process the results of a SOQL query, especially when dealing with large datasets. They automatically manage memory by retrieving records in batches, making them ideal for avoiding heap size governor limits.

Syntax:

for (sObject record : [SELECT Id, Name FROM MyObject WHERE ...]) {
    // Process each record here
}

Key Advantages:

  • Automatic Batching: Salesforce handles fetching records in chunks (typically 200 records at a time) and passes them to your loop. This prevents exceeding heap size limits for large query results.
  • Memory Efficiency: Records are processed and then discarded, ensuring memory consumption remains low.
  • Governor Limit Awareness: While the loop iterates, the query itself still counts as one SOQL query. DML operations inside the loop are subject to DML limits per transaction, so bulkification is still crucial.

Example: Processing a Large Number of Leads

public class LeadProcessor {
    public static void processLeads() {
        // Querying a potentially large number of Leads
        for (Lead l : [SELECT Id, FirstName, LastName, Email, Status FROM Lead WHERE Status = 'New']) {
            // Process each lead
            // Example: Update lead status if email is present
            if (l.Email != null) {
                l.Status = 'Contacted';
                // Accumulate updates in a list and perform DML outside the loop for bulkification
                // (Though for loops handle fetching, DML is still per-record if not bulkified)
            }
            System.debug('Processing Lead: ' + l.Id + ' - ' + l.Status);
        }
        // If updates were accumulated, perform update DML here
        // update leadsToUpdate;
    }

    // A better bulkified example using a list
    public static void updateProcessedLeads() {
        List<Lead> leadsToUpdate = new List<Lead>();
        for (Lead l : [SELECT Id, FirstName, LastName, Email, Status FROM Lead WHERE Status = 'New']) {
            if (l.Email != null) {
                l.Status = 'Contacted';
                leadsToUpdate.add(l);
            }
        }
        if (!leadsToUpdate.isEmpty()) {
            update leadsToUpdate; // Single DML operation
            System.debug('Updated ' + leadsToUpdate.size() + ' leads.');
        }
    }
}

When to use a SOQL For Loop vs. a List:

  • Use SOQL For Loop: When you expect a large number of records (hundreds to thousands or more) and need to iterate through them without consuming excessive heap space. This is the preferred method for querying and processing large datasets in Apex.
  • Use a `List<sObject>` with direct assignment: When you expect a small number of records (up to a few hundred) and need to perform subsequent operations on the entire collection, or when you explicitly need the entire list in memory.
    List<Account> smallAccountsList = [SELECT Id, Name FROM Account WHERE NumberOfEmployees < 10];
    // You have the entire list in memory now
    System.debug('Small list size: ' + smallAccountsList.size());
    

Always prioritize SOQL For Loops for processing query results within triggers or batch jobs to prevent hitting heap size limits.

6. Introduction to SOSL

SOSL (Salesforce Object Search Language) is a powerful search language designed for performing text-based searches across multiple sObject types and fields. It's ideal when you need to find records that contain specific words or phrases, but you don't necessarily know which object or field contains that information.

When to use SOSL?

  • When you need to search for a phrase across multiple objects.
  • When you need to search for a phrase within a single object, but across multiple fields.
  • When you need to retrieve records based on fuzzier text matching rather than precise field values.
  • When performing searches similar to a global search in the Salesforce UI.

Key Characteristics:

  • Searches across multiple objects.
  • Returns a list of lists of sObjects (List<List<sObject>>), where each inner list contains records of a specific object type.
  • Can search fields like `Name`, `Email`, `Phone`, `TextArea`, `LongTextArea`, `RichText`, `ComboBox`.
  • Uses a search index for faster results.
  • Supports stemming (e.g., searching for "run" also finds "running", "ran").

7. SOSL Syntax & Features

Basic Syntax:

FIND 'Search Term' [IN SearchGroup] [RETURNING ObjectTypeName [ (FieldList) ] [WITH Network]] [LIMIT Number] [OFFSET Number]

Let's break down the common clauses:

  • FIND 'Search Term': The phrase or term you are looking for. Can include wildcards (`*` for zero or more characters, `?` for a single character). Use single quotes.
  • IN SearchGroup: Specifies which fields to search within. Options include:
    • `ALL FIELDS` (default)
    • `NAME FIELDS`
    • `EMAIL FIELDS`
    • `PHONE FIELDS`
    • `SIDEBAR FIELDS` (searchable in sidebar)
  • RETURNING ObjectTypeName [(FieldList)]: Specifies which sObjects and their fields to return. If `FieldList` is omitted, only the `Id` is returned. You can specify multiple objects.
  • WITH Network: Used for Chatter Answers or Salesforce Communities searches.
  • LIMIT Number: Limits the number of rows returned per sObject type.
  • OFFSET Number: Skips records per sObject type.

Examples:

Basic Search across all searchable objects for 'GenePoint':

FIND 'GenePoint' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, FirstName, LastName)

Search for a phrase and include a wildcard:

FIND 'uni*' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, Name)

Search for a phone number:

FIND '415-555-1212' IN PHONE FIELDS RETURNING Contact(Id, Name, Phone), Lead(Id, Name, Phone)

Using SOSL in Apex:

In Apex, you embed SOSL queries directly within square brackets `[]`. The query returns a `List<List<sObject>>`.

List<List<SObject>> searchResults = [FIND 'Acme' IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, FirstName, LastName, Email)];

// Iterate through the results, casting each inner list to the specific sObject type
for (List<SObject> sObjectList : searchResults) {
    if (sObjectList.isEmpty()) {
        continue;
    }
    SObjectType objectType = sObjectList[0].getSObjectType();

    if (objectType == Account.SObjectType) {
        for (Account acc : (List<Account>)sObjectList) {
            System.debug('Found Account: ' + acc.Name + ' (' + acc.Id + ')');
        }
    } else if (objectType == Contact.SObjectType) {
        for (Contact con : (List<Contact>)sObjectList) {
            System.debug('Found Contact: ' + con.FirstName + ' ' + con.LastName + ' (Email: ' + con.Email + ')');
        }
    }
}

8. Choosing Between SOQL & SOSL

Deciding between SOQL and SOSL is critical for efficient data retrieval. Here's a guide:

Use SOQL When:

  • You know which object(s) you need to query.
  • You need to retrieve specific fields from a single object or closely related objects (parent-to-child or child-to-parent).
  • You want to retrieve records based on specific field values (e.g., `WHERE Status = 'Active'`).
  • You need to perform aggregate functions (SUM, AVG, COUNT, etc.).
  • You need to order or group results.
  • You need to use `OFFSET` for pagination.
  • You need to query across more than one object, and the objects are related (e.g., querying `Contact` and its `Account`).
// Example SOQL use case: Get specific fields from a related Account & its Contacts
SELECT Id, Name, AnnualRevenue, (SELECT Id, FirstName, LastName FROM Contacts)
FROM Account
WHERE Industry = 'Technology' AND AnnualRevenue > 500000

Use SOSL When:

  • You need to search for text across multiple objects (e.g., "Find all records containing 'Sales'").
  • You need to search across multiple fields on a single object without specifying each field explicitly.
  • You don't know which specific object or field contains the data you're looking for, but you have a search term.
  • You want to perform a Google-like search within Salesforce data.
// Example SOSL use case: Search for a term across Accounts and Leads
FIND 'Marketing Campaign' IN ALL FIELDS
RETURNING Account(Id, Name, Industry), Lead(Id, Name, Status, Company)

Think of it this way:

  • SOQL is like a targeted database query: "Give me all Accounts with X criteria, and their related Contacts."
  • SOSL is like a search engine: "Find 'X' anywhere it appears in my data, across different object types."

9. SOQL & SOSL Best Practices

Writing efficient and scalable SOQL and SOSL queries is paramount for avoiding governor limits and ensuring optimal application performance.

General Best Practices:

  • Avoid SOQL/SOSL Inside Loops: This is a critical governor limit violation. Never put a SOQL query or SOSL search inside a `for` loop, as it quickly consumes your query limits (100 SOQL queries, 20 SOSL queries per transaction).
  • Bulkify Your Queries: Always design your code to handle collections of records. If you need to query related data, collect all IDs first, then perform a single query (or use relationship queries).
  • Select Only Necessary Fields: Don't use `SELECT *` (not supported in Apex SOQL anyway). Retrieve only the fields you actually need. This reduces heap size and improves performance.
  • Use Query Plan Tool: In the Developer Console, use the "Query Plan" tool (Ctrl+I) to analyze the performance of your SOQL queries, especially on large objects. It tells you which fields are indexed and if a query will be performant.
  • Consider Custom Indexes: If you frequently filter by a custom field that is not indexed, consider marking it as "External ID" or "Unique" to create an index. Salesforce automatically indexes standard ID fields, foreign key relationships, and standard fields with specific attributes.

SOQL Specific Best Practices:

  • Filter Your Queries (Use `WHERE` Clause): Always try to include a `WHERE` clause to restrict the number of records returned. Querying large numbers of records without filters can be inefficient and hit governor limits.
  • Order of Operations (Filtering before Sorting/Limiting): Ensure your `WHERE` clause is as selective as possible to filter records *before* sorting or limiting, which is more efficient.
  • Use Relationship Queries Wisely: While powerful, deeply nested relationship queries can impact performance. Avoid excessive nesting if possible.
  • SOQL For Loops for Large Datasets: As discussed, use SOQL For Loops to efficiently process large query results without hitting heap limits.
// BAD: Query inside a loop (hits 100 SOQL query limit quickly)
for (Account acc : Trigger.new) {
    // This query runs for EACH account!
    Contact c = [SELECT Id FROM Contact WHERE AccountId = :acc.Id AND Name = 'Primary Contact'];
    // ...
}

// GOOD: Bulkified query outside the loop
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) {
    accountIds.add(acc.Id);
}
// Single query to get all relevant contacts for all accounts
List<Contact> contacts = [SELECT Id, Name, AccountId FROM Contact WHERE AccountId IN :accountIds AND Name = 'Primary Contact'];
// Now process the results using maps for efficient lookup
Map<Id, Contact> contactsByAccountId = new Map<Id, Contact>();
for (Contact con : contacts) {
    contactsByAccountId.put(con.AccountId, con);
}
for (Account acc : Trigger.new) {
    Contact primaryContact = contactsByAccountId.get(acc.Id);
    if (primaryContact != null) {
        // ...
    }
}

SOSL Specific Best Practices:

  • Specificity in `FIND` Clause: Be as specific as possible with your search terms to get more relevant results.
  • Use `IN SearchGroup` Judiciously: If you know the search will only be in, say, name fields, specify `IN NAME FIELDS` instead of `IN ALL FIELDS` for better performance.
  • Limit `RETURNING` Objects and Fields: Only return the objects and fields that you genuinely need.
  • Performance Considerations: SOSL queries can sometimes be slower than highly selective SOQL queries, especially on very large orgs with many text fields. Use SOSL when the "search" paradigm is truly appropriate.

By following these best practices, you can ensure your SOQL and SOSL queries are performant, respect governor limits, and contribute to a robust Salesforce application.