Apex Basics & Database

Get Started with Apex

Describe the key features of the Apex programming language. Save an Apex class and call methods with Anonymous.Apex. Use the Developer Console to inspect debug logs.
  • Resources from Trailhead:
  • Get Started with Apex
    • Apex enables developers to add business logic to system events, like button clicks, updates of related records, and Visualforce pages.
    • Apex is Java-like and acts like database stored procedures. Other characteristics:
      • Hosted: saved, compiled, executed on Lightning Platform servers
      • Object oriented: supports classes, interfaces, and inheritance
      • Strongly typed: validates references to objects at compile time
      • Multitenant aware: guards closely against runaway code by enforcing limits, preventing code from monopolizing shared resources
      • Integrated with the database: straightforward to access and manipulate records. Provides direct access to records and fields, and gives statements and query languages to manipulate those records.
      • Data focused: provides transactional access to the database, which allows you to roll back operations
      • Easy to use: based on familiar Java idioms
      • Easy to test: provides build-in support for unit test creation, execution, and code coverage. Ensures that all custom Apex code works as expected by executing all unit tests before platform upgrades.
      • Versioned: custom Apex code can be saved against different versions of the API
      • Case-insensitive
    • Similar to other object-oriented programming languages by supporting:
      • Classes, interfaces, properties, collections (arrays)
      • Object and array notation
      • Expressions, variables, constants
      • If-then-else (conditional statements)
      • For loops, while loops (control flow statements)
    • Different from other object-oriented programming languages by supporting:
      • Stored, compiled, executed in the cloud (Cloud development)
      • Triggers, similar to triggers in database systems
      • Database statements that allow you to make direct database calls
      • Transactions and rollbacks
      • global access modifier is more permissive than public, allowing access across namespaces/applications
      • Versioning of custom code

  • Apex supports several data types:
    • Primitives: Integer, Double, Long, Date, Datetime, String, ID, Boolean
    • sObjects: both generic and specific, such as Account, Contact, or MyCustomObject__c
      • Specific to Salesforce
    • Collections:
      • List (or array) of primitives, sObjects, user defined objects, objects created from Apex classes, collections
      • Set of primitives
      • Map from a primitive to a primitive, sObject, or collection
    • Enum: typed list of values
    • Apex Classes:
      • User-Defined
      • System-Supplied
  • Apex Collections: List (Apex also supports Set and Map collections. Reference the collections of Apex Developer Guide.)
    • Lists are synonymous with arrays and can be used interchangeably
      • Array: List<String> colors = new List<String>();
      • List: String[] colors = new List<String>();
      • Its easier to create a list than an array most of the time, because lists don’t require specifying the length ahead of time.
    • Add elements to a list during creation
      • List<String> colors = new List<String> { 'red', 'green', 'blue' };
    • Or: Add elements afterwards by calling the add() method
      • List<String> moreColors = new List<String>();
      • moreColors.add('orange');
    • List elements can be read by specifying an index between square brackets, just like with array elements:
      • String color2 = moreColors[0];
    • Can also use get() to read a list element:
      • String color1 = moreColors.get(0);
    • Example of iterating over array elements:
for(Integer i=0;i<colors.size();i++) {
    // Write value to the debug log
    System.debug(colors[i]);
}
  • Apex Classes
    • Benefit of Apex classes is code reuse. Class methods can be called by triggers and other classes.
    • Save an Apex Class to your org via: Setup > Developer Console > File | New | Apex Class
  • Example class from Trailhead, below
    • Class below makes use of object-oriented programming. It encapsulates the methods related to managing email.
public class EmailManager {
    // Public method
    public void sendMail(String address, String subject, String body) {
        // Create an email message object
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {address};
        mail.setToAddresses(toAddresses);
        mail.setSubject(subject);
        mail.setPlainTextBody(body);
        // Pass this email message to the built-in sendEmail method
        // of the Messaging class
        Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                 new Messaging.SingleEmailMessage[] { mail });

        // Call a helper method to inspect the returned results
        inspectResults(results);
    }

    // Helper method
    private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
        Boolean sendResult = true;

        // sendEmail returns an array of result objects.
        // Iterate through the list to inspect results.
        // In this class, the methods send only one email,
        // so we should have only one result.
        for (Messaging.SendEmailResult res : results) {
            if (res.isSuccess()) {
                System.debug('Email sent successfully');
            }
            else {
                sendResult = false;
                System.debug('The following errors occurred: ' + res.getErrors());                 
            }
        }

        return sendResult;
    }
}
  • A perfect example of OOP would also contain member variables (attributes) and accessor methods to access them - for simplicity these aren’t included above.

Call a Method

  • Anonymous Apex allows you to run lines of code on the fly - handy way to invoke Apex and test functionality.
    • Can also invoke Apex through triggers, for example.
  • Execute anonymous apex via: Developer Console > Debug | Open Execute Anonymous Window
EmailManager em = new EmailManager();
em.sendMail('Your email address', 'Trailhead Tutorial', '123 body');

Inspect Debug Logs

  • Debug logs are useful for debugging your code. When Apex methods execute, the calls are logged in the debug log.
    • The inspectResults() helper method in the script above is called by sendMail() to write messages to the log using the System.debug() method to indicate whether the email send operation was successful.
    • Select the “Debug Only” checkbox to filter the log such that only log lines for System.debug() statements are shown.
      • A line with DEBUG|Email sent successfully

Call a Static Method

  • Since the sendMail() method in our class doesn’t access class member variables, it can be a static (not an instance) method.
  • Convert to a static method by adding the word static to the method declaration.
  • Then, change the method call from the following:
EmailManager em = new EmailManager();
em.sendMail('Your email address', 'Trailhead Tutorial', '123 body');
  • To the following:
EmailManager.sendMail('Your email address', 'Trailhead Tutorial', '123 body');

Example Apex Class

  • Example Apex class with a method that returns an array (or list) of strings:
public class StringArrayTest {
    public static List<String> generateStringArray (Integer numStrings) {
        List<String> stringList = new List<String>();
        for (Integer i=0; i<numStrings; i++) {
            stringList.add('Test ' + i);
        }
        return stringList;
    }
}
  • Execute Anonymous code to call the class above
StringArrayTest.generateStringArray(3);

Use sObjects

Describe the relationship between sObjects and Salesforce records. Create and use specific sObject variables. Cast a generic sObject to a specific sObject.
  • Apex is tightly integrated with the database, so you can access Salesforce records and fields directly from Apex.
    • Each record in Salesforce is represented as an sObject in Salesforce.
    • Account sObject is an abstraction of the Acme account example record. It holds the account field info in memory as an object.
    • Each record is represented as an sObject before it is inserted into/retrieved from Salesforce.

  • To create an sObject, you need to declare a variable and assign it to an sObject instance.
    • Names of sObjects correspond to API names of corresponding standard or custom objects.
    • Names for sObject fields correspond to the API names of corresponding fields.
      • Custom objects and fields have API names that end with __c suffix.
      • Custom relationship fields have API names that end with __r suffix.
  • Before you can insert a Salesforce record, you must create it in memory first as an sObject. These are created with the new operator.
    • Populate fields by passing them in through the construtor or assigning them after the fact using dot notation:
Account acct = new Account();
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
Account acct = new Account();
acct.Name = 'Acme';
acct.Phone = '(415)555-1212';
acct.NumberOfEmployees = 100;

Working with the Generic sObject Data Type

  • If you don’t know the specific sObject data type, such as Account, you can use the generic sObject data type.
    • Variables that are declared with the generic sObject data type can reference any Salesforce record.

sObject sobj1 = new Account(Name='Trailhead');
sObject sobj2 = new Book__c(Name='Workbook 1');
  • sObject is a parent type for all specific sObjects.
    • So, you can “cast” your sObject variable to a specific sObject type.
    • Benefits of doing this is being able to access fields using dot notation, which isn’t available on the generic sObject.
// Cast a generic sObject to an Account
Account acct = (Account)myGenericSObject;
// Now, you can use the dot notation to access fields on Account
String name = acct.Name;
String phone = acct.Phone;
  • Generic sObjects can be created only through the newSOjbect() method.
  • Fields of a generic sObject can only be access through put() and get() methods.
  • Creating an sObject doesn’t persist it as a record in the database.
    • To save an sObject as a record, we use Data Manipulation Language (DML)
    • To retrieve a record, use Salesforce Object Query Language (SOQL)

  • Relationship between sObjects and Salesforce records:
    • Every record in Salesforce is natively represented as an sObject in Apex.
  • You can obtain an instance of an sObject by:
    • Either creating the sObject or by retrieving a persistent record from Salesforce using SOQL.
  • A generic sObject variable can be assigned to any specific sObject, standard or custom, like Account or Book__c.

Manipulate Records with DML

Use DML to insert, update, and delete records. Perform DML statements in bulk. Use upsert to either insert or update a record. Catch a DML Exception. Use a Database method to insert new records with the partial success option and process the results. Know when to use DML statements and when to use Database methods. Perform DML operations on related records.

Manipulate Records with DML

  • Data Manipulation Language (DML) provides a straightforward way to manage records, including simple statements to insert, update, upsert, delete, undelete, and merge records.
    • Since Apex is a data-focused language it has access to your data in Salesforce - no additional setup is needed to connect data sources, unlike other languages.
    • Each DML statement accepts either a single sObject or a list (or array) of sObjects.
      • upsert DML operation creates new records and updates existing sObject records within a single statement.
        • It uses a specific field to determine the presence of existing objects, or the ID if not otherwise specified.
      • merge DML statement merges up to three records of the same sObject into one of the records, deleting the others, and re-parenting any related records.
// Create the account sObject
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
// Insert the account by using DML
insert acct;
  • ID field Auto-Assigned to new records
    • When inserting records, the system assigns an ID for each record and autopopulates it on the sObject variable you used as an argument in the DML call.
    • Since the sObject variable contains the ID after the DML call, you can reuse this sObject variable to perform further DML operations, like updates.
    • Also possible to retrieve a record to obtain its fields, but this can’t be done with DML - need to write a query using SOQL for this.
  • Run the following in Developer Console > Open Execute Anonymous Window:
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
insert acct;
ID acctID = acct.Id; // Get the new ID on the inserted sObject argument
System.debug('ID = ' + acctID);

Bulk DML

  • You can perform DML operations either on a single sObject or in bulk on a list of sObjects.
    • Bulk DML oeprations aare recommended as they help avoid hitting governor limits - Ex: DML limit of 150 statements per Apex transaction. This limit is in place to ensure fair access to shared resources on the Lightning Platform.
    • Performing a DML operation on a list of sObjects counts as one DML statement, not as one stataement for each object.
  • Example of bulkified Inserts and Updates:
// Create a list of contacts
List<Contact> conList = new List<Contact> {
    new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
    new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
    new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
    new Contact(FirstName='Kim',LastName='Shain',Department='Education')};

// Bulk insert all contacts with one DML call
insert conList;
// List to hold the new contacts to update
List<Contact> listToUpdate = new List<Contact>();
// Iterate through the list and add a title only
//   if the department is Finance
for(Contact con : conList) {
    if (con.Department == 'Finance') {
        con.Title = 'Financial analyst';
        // Add updated contact sObject to the list.
        listToUpdate.add(con);
    }
}
// Bulk update all contacts with one DML call
update listToUpdate;

Upserting Records

  • If you have a list containing a mix of new and existing records, you can process insertions and updates at the same time using upsert.
    • Upsert matches the sObjects with existing records by comparing values of one field. If you don’t specify the field, the upsert uses the sObject’s ID to match on.
    • Alternatively, can specify a field to use for matching.
      • Custom objects: can specify a custom field marked as external ID.
      • Standard objects: can specify any field that has idLookup set to true (Ex: Email field of Contact or User).
  • Upsert Syntax: upsert sObject | sObject[] or upsert sObject | sObject[]​​ field
upsert sObjectList Account.Fields.MyExternalId;
// Insert the Josh contact
Contact josh = new Contact(FirstName='Josh',LastName='Kaplan',Department='Finance');       
insert josh;
// Josh's record has been inserted
//   so the variable josh has now an ID
//   which will be used to match the records by upsert
josh.Description = 'Josh\'s record has been updated by the upsert operation.';
// Create the Kathy contact, but don't persist it in the database
Contact kathy = new Contact(FirstName='Kathy',LastName='Brown',Department='Technology');
// List to hold the new contacts to upsert
List<Contact> contacts = new List<Contact> { josh, kathy };
// Call upsert
upsert contacts;
// Result: Josh is updated and Kathy is created.
Contact jane = new Contact(FirstName='Jane',
                           LastName='Smith',
                           Email='[email protected]',
                           Description='Contact of the day');
insert jane;
// 1. Upsert using an idLookup field
// Create a second sObject variable.
// This variable doesn’t have any ID set.
Contact jane2 = new Contact(FirstName='Jane',
                            LastName='Smith',  
                            Email='[email protected]',
                            Description='Prefers to be contacted by email.');
// Upsert the contact by using the idLookup field for matching.
upsert jane2 Contact.fields.Email;
// Verify that the contact has been updated
System.assertEquals('Prefers to be contacted by email.',
                   [SELECT Description FROM Contact WHERE Id=:jane.Id].Description);

Deleting Records

  • Deleted records are placed in the Recycle Bin for 15 days, from which they can be restored.
  • Executing the following snippet in Developer Console using Anonymous Apex will remove all contacts with last name “Smith”
    • Snippet below includes a query to retrieve the contacts (SOQL)
Contact[] contactsDel = [SELECT Id FROM Contact WHERE LastName='Smith'];
delete contactsDel;

DML Statement Exceptions

  • If a DML operation fails, it returns an exception of type DmlException.
    • You can catch exceptions in your code to handle error conditions.
  • Example below produces a DmlException because it attempts to insert an account without the required Name field. The exception is caught in the catch block.
try {
    Account acct = new Account(); // Causes exception - the required Name field is not provided.
    insert acct; // Insert the account
} catch (DmlException e) {
    System.debug('A DML exception has occurred: ' +
                e.getMessage());
}

Database Methods

  • Apex contains the built-in Database class, which provides methods that perform DML operations. The methods are static and called on the class name.
    • Database.insert()
    • Database.update()
    • Database.upsert()
    • Database.delete()
    • Database.undelete()
    • Database.merge()
  • These methods have an optional allOrNone parameter that lets you specify whether the operation should partially succeed.
    • If errors occur on a partial set of records, successful records will be committed and errors will be returned for the failed records.
    • Ex: Database.insert(recordList, false);
  • Return result includes success or failure information for each record.
    • Ex: Database.SaveResult[] results = Database.insert(recordList, false);
      • Upsert returns Database.UpsertResult objects, delete returns Database.DeleteResult objects
  • By default, allOrNone parameter is true
  • As an example, running the following example code in the Execute Anonymous window produces the result in the image that follows it.

  • DML Statements or Database Methods?
    • Use DML statements if you want any error that occurs during bulk DML processing to be thrown as an Apex exception that immediately interrupts control flow (try...catch blocks).
      • Similar to how exceptions are handled in most database procedural languages.
    • Use Database class methods if you want to allow partial success of a bulk DML operation.
      • Application can then inspect the rejected records and possibly retry the operation.
      • When using this form, you can write code that never throws DML exception errors. Instead the code can use the results arrays to judge success or failure and iterate.
  • Inserting Related Records
    • Can insert records related to existing records if a relationship has already been defined between the two objects, such as a lookup or master-detail relationship.
      • A record is associated with a related record through a foreign key ID.
      • Ex: inserting a new contact, you can specify the contact’s related account record by setting the value of AccountId field.
    • Example code showing how to add a contact to an account, below. A new account (SFDC Account) was created and has the Mario Ruiz contact in the account’s Contacts related list.
Account acct = new Account(Name='SFDC Account');
insert acct;
// Once the account is inserted, the sObject will be
// populated with an ID.
// Get this ID.
ID acctID = acct.ID;
// Add a contact to this account.
Contact mario = new Contact(
    FirstName='Mario',
    LastName='Ruiz',
    Phone='415.555.1212',
    AccountId=acctID);
insert mario;
  • Updating Related Records
    • Fields on a related record can’t be updated with the same call to the DML operation. They require a separate DML call.
    • Example code below updates a contact and its related account using two subsequent update statements.
// Query for the contact, which has been associated with an account.
Contact queriedContact = [SELECT Account.Name
                          FROM Contact
                          WHERE FirstName = 'Mario' AND LastName='Ruiz'
                          LIMIT 1];
queriedContact.Phone = '(415)555-1213'; // Update the contact's phone number
queriedContact.Account.Industry = 'Technology'; // Update the related account industry
// Make two separate calls
// 1. This call is to update the contact's phone.
update queriedContact;
// 2. This call is to update the related account's Industry field.
update queriedContact.Account;
  • Deleting Related Records
    • delete supports cascading deletions. If you delete a parent object, you delete its children automatically, as long as each child record can be deleted.
  • Ex: deleting the SFDC Account created with snippet above also deletes its related contact.
Account[] queriedAccounts = [SELECT Id FROM Account WHERE Name='SFDC Account'];
delete queriedAccounts;
  • DML operations execute within a transaction.
    • All DML operations in a transaction either complete successfully, or if an error occurs in one operation, the entire transaction is rolled back and no data is committed.
    • Ex: if a trigger/class creates two accounts and updates one contact, and the contact update fails because of a validation rule failure, the entire transaction rolls back. none of the accounts are persisted in Salesforce.

  • Resources:

Example: Create a method for inserting accounts named after an incoming parameter.

public class AccountHandler {
    public static Account insertNewAccount (String name) {
        Account acct = new Account(Name=name);
        try {
            insert acct;
        } catch (DmlException e) {
            return Null;
        }
        return acct;
    }
}

To test the new method:

System.debug('ID = ' + AccountHandler.insertNewAccount('Test').ID);

The debug log shows:

Write SOQL Queries

Write SOQL queries in Apex. Execute SOQL queries by using the Query Editor in the Developer Console. Execute SOQL queries embedded in Apex by using Anonymous Apex. Query related records.
  • To read a record from Salesforce, you query the platform using SOQL, or “Salesforce Object Query Language." SOQL is similar to standard SQL but is customized for the Lightning Platform.
  • Apex has direct access to Salesforce records that are stored in the database, so you can embed SOQL queries in your Apex code. Embedded SOQL is referred to as inline SOQL.
    • To include SOQL queries within your Apex, wrap the SOQL statement within square brackets and assign the return value to an array of sObjects. Example:
Account[] accts = [SELECT Name,Phone FROM Account];
  • Execute the following in a trailhead org to create some sample data.
Account acct = new Account(
    Name='SFDC Computing',
    Phone='(415)555-1212',
    NumberOfEmployees=50,
    BillingCity='San Francisco');
insert acct;
ID acctID = acct.ID;

// Add a contact to this account.
Contact con = new Contact(
    FirstName='Carol',
    LastName='Ruiz',
    Phone='(415)555-1212',
    Department='Wingo',
    AccountId=acctID);
insert con;

// Add account with no contact
Account acct2 = new Account(
    Name='The SFDC Query Man',
    Phone='(310)555-1213',
    NumberOfEmployees=50,
    BillingCity='Los Angeles',
    Description='Expert in wing technologies.');
insert acct2;

Query Editor

  • The Query Editor is provided in the Developer Console. This enables you to run SOQL queries and view results. Reference the Query Editor tab.

Basic SOQL Syntax

  • The basic syntax of an SOQL query: SELECT fields FROM ObjectName [WHERE Condition]
    • Ex: SELECT Name,Phone FROM Account
      • SELECT Name,Phone - lists fields to retrieve.
      • FROM Account - specifies the standard or custom object you want to retrieve.
    • Unlike other SQL languages, in SOQL you can’t specify * for all fields. They must be specified explicitly.

Filter Query Results with Conditions

  • The WHERE clause retrieves only the records that match specific criteria.
    • Ex: SELECT Name,Phone FROM Account WHERE Name='SFDC Computing'
    • Ex: SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' AND NumberOfEmployees>25)
    • Ex: SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' OR (NumberOfEmployees>25 AND BillingCity='Los Angeles'))
  • Can perform fuzzy matches using the LIKE operator.
    • Ex: can retrieve all accounts whose names start with SFDC by using WHERE Name LIKE SFDC%
      • The % wildcard character matches any or no character
      • The _ character matches just one character

Ordering Query Results

  • An ORDER BY clause lets you order results based on most types of fields, including numeric and text fields.
  • ASC and DESC let you control the ordering as ascending or descending.
    • Ex: SELECT Name,Phone FROM Account ORDER BY Name
    • Ex: SELECT Name,Phone FROM Account ORDER BY Name ASC
    • Ex: SELECT Name,Phone FROM Account ORDER BY Name DESC

Limiting the Number of Records Returned

  • Use LIMIT n to reduce the size of the return set:
    • Account oneAccountOnly = [SELECT Name,Phone FROM Account LIMIT 1];

Examples

SELECT Name,Phone FROM Account
                   WHERE (Name = 'SFDC Computing' AND NumberOfEmployees>25)
                   ORDER BY Name
                   LIMIT 10
Account[] accts = [SELECT Name,Phone FROM Account
                   WHERE (Name='SFDC Computing' AND NumberOfEmployees>25)
                   ORDER BY Name
                   LIMIT 10];
System.debug(accts.size() + ' account(s) returned.');
// Write all account array info
System.debug(accts);

Accessing Varaiables in SOQL Queries

  • SOQL statements can reference Apex code variables and expressions.
    • Precede them by a colon (:), called a bind.
String targetDepartment = 'Wingo';
Contact[] techContacts = [SELECT FirstName,LastName
                          FROM Contact WHERE Department=:targetDepartment];
  • Records in Salesforce are linked through relationships (lookup, master-detail).
    • To get child records related to a parent record, add an inner query for the child records. The FROM clause runs against the relationship name, no the Salesforce object name.
    • Ex: SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing'
Account[] acctsWithContacts = [SELECT Name, (SELECT FirstName,LastName FROM Contacts)
                               FROM Account
                               WHERE Name = 'SFDC Computing'];
// Get child records
Contact[] cts = acctsWithContacts[0].Contacts;
System.debug('Name of first associated contact: '
             + cts[0].FirstName + ', ' + cts[0].LastName);
  • We can traverse a relationship from a child object (contact) to a field on its parent (Account.Name) by using dot notation.
Contact[] cts = [SELECT Account.Name FROM Contact
                 WHERE FirstName = 'Carol' AND LastName='Ruiz'];
Contact carol = cts[0];
String acctName = carol.Account.Name;
System.debug('Carol\'s account name is ' + acctName);

Querying in Batches by using SOQL For Loops

  • SOQL for loops let you include a SOQL query within a for loop. The results if a SOQL query can be iterated over within the loop.
    • These loops use a different method for retrieving records: efficient chunking with calls to the query and queryMore methods of the SOAP API.
      • This allows you to avoid hitting the heap size limit.
for (variable : [soql_query]) {
    code_block
}

OR

for (variable_list : [soql_query]) {
    code_block
}
  • Both variable and variable_list must be of the same type as the sObjects that are returned by the soql_query.
    • Preferable to use the sObject list format of the SOQL for loop as the loop executes once for each batch of 200 sObjects.
insert new Account[]{new Account(Name = 'for loop 1'),
                     new Account(Name = 'for loop 2'),
                     new Account(Name = 'for loop 3')};
// The sObject list format executes the for loop once per returned batch
// of records
Integer i=0;
Integer j=0;
for (Account[] tmp : [SELECT Id FROM Account WHERE Name LIKE 'for loop _']) {
    j = tmp.size();
    i++;
}
System.assertEquals(3, j); // The list should have contained the three accounts
                       // named 'yyy'
System.assertEquals(1, i); // Since a single batch can hold up to 200 records and,
                       // only three records should have been returned, the
                       // loop should have executed only once

Example Apex class that returns contacts based on query parameters

public class ContactSearch {
    public static List<Contact> searchForContacts(String lastname, String postalCode) {
        Contact[] cts = [SELECT Contact.Id, Contact.Name FROM Contact
                         WHERE LastName = :lastname AND MailingPostalCode = :postalCode];
        return cts;
    }
}

Write SOSL Queries

Describe the differences between SOSL and SOQL. Search for fields across multiple objects using SOSL queries. Execute SOSL queries by using the Query Editor in the Developer Console.
  • SOSL (Salesforce Object Search Language) is a Salesforce search language that performs text searches in records.
    • Searches across multiple standard and custom object records.
    • Similar to “Apache Lucene”
  • Adding SOSL to Apex is simple - you can embed SOSL queries directly in Apex. This is called Inline SOSL. Example:
List<List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS
                                  RETURNING Account(Name), Contact(FirstName,LastName)];
  • SOQL and SOSL are similar:
    • Both allow you to search your organization’s records for info
  • SOQL and SOSL are different:
    • SOQL searches one object at a time, SOSL searches all objects
    • SOQL performs an exact match by default, SOSL matches fields based on word match
      • SOSL: ‘Digital’ returns ‘The Digital Company’
      • SOQL: ‘Digital’ returns ‘Digital’

  • Query Editor tab in the Developer console allows you to run SOSL queries and view results.
    • Results are grouped in tabs for each object
FIND {Wingo} IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName,Department)
  • Note:
    • Search query in the Query Editor and the API must be enclosed within curly brackets {SFDC}
    • Search query in Apex must be enclosed within single quotes 'Wingo'

SOSL Syntax

  • SOSL allows you to specify specific search criteria:
    • Text expression (single word or phrase) to find
    • Scope of fields to search
    • List of objects and fields to retrieve
    • Conditions for selecting rows in source objects
FIND 'SearchQuery' [IN SearchGroup] [RETURNING ObjectsAndFields]
  • SearchQuery: text to search for.
    • Can be grouped with logical operators (AND, OR) and parentheses.
    • Can include wildcard characters (*, ?)
      • *: matches zero or more characters at the middle or end of the search term
      • ?: matches only one character at the middle or end of the search term
  • SearchGroup: optional, scope of the fields to search
    • If not specified, default scope is all fields
    • Can take one of the following values:
      • ALL FIELDS, NAME FIELDS, EMAIL FIELDS, PHONE FIELDS, SIDEBAR FIELDS
  • ObjectsAndFields: optional, info to return in the search result.
    • Info to return in the search result. List of one or more sObjects and one or more fields.

  • Example of how to run an SOSL query in Apex:
    • Results are returned in a list of lists. Each list contains an array of the returned records. Ex:
      • Index 0: list contains array of Accounts
      • Index 1: list contains the array of Contacts
List<List<sObject>> searchList = [FIND 'Wingo OR SFDC' IN ALL FIELDS
                                  RETURNING Account(Name),Contact(FirstName,LastName,Department)];
Account[] searchAccounts = (Account[])searchList[0];
Contact[] searchContacts = (Contact[])searchList[1];
System.debug('Found the following accounts.');
for (Account a : searchAccounts) {
    System.debug(a.Name);
}
System.debug('Found the following contacts.');
for (Contact c : searchContacts) {
    System.debug(c.LastName + ', ' + c.FirstName);
}

  • Possible to filter, reorder, and limit results of SOSL query using the RETURNING clause.
    • Add WHERE to filter. Ex: RETURNING Account(Name, Industry WHERE Industry='Apparel')
    • Add ORDER BY to order. Ex: RETURNING Account(Name, Industry ORDER BY Name)
    • Add LIMIT to subset the records. Ex: RETURNING Account(Name, Industry LIMIT 10)

Example Apex class that returns both Contacts and Leads based on an input parameter

public class ContactAndLeadSearch {
    public static List<List<sObject>> searchContactsAndLeads (String searchName) {
        List<List<sObject>> queryResults = [FIND :searchName IN ALL FIELDS RETURNING Contact(FirstName,LastName), Lead(FirstName,Lastname)];
        return queryResults;
    }
}

To execute this method using Execute Anonymous:

System.Debug(ContactAndLeadSearch.searchContactsAndLeads('Smith'));