Process Automation and Logic · Topic 3 of 7

SOQL, SOSL and DML in Apex

SOQL and SOSL in Apex with bind variables, relationship and aggregate queries, date literals, SOSL search, DML statements, Database methods and upsert.

  • 4 min
In a nutshell
  • SOQL reads one object and its relationships; SOSL searches text across objects.
  • Bind variables (:var) prevent SOQL injection.
  • A query assigned to a single sObject must return exactly one row.

What it is

Apex reads data with two query languages and writes it with DML.

  • SOQL (Salesforce Object Query Language) reads records from one object and its related objects, like SQL SELECT.
  • SOSL (Salesforce Object Search Language) runs a text search across several objects at once.
  • DML (Data Manipulation Language) inserts, updates, upserts, deletes, undeletes and merges records.
SOQLone object and relationsRecords in ApexChange in memoryDMLinsert, update, delete

How it works

SOQL in Apex

String stage = 'Closed Won';
List<Opportunity> opps = [
    SELECT Id, Name, Amount, Account.Name
    FROM Opportunity
    WHERE StageName = :stage AND CloseDate = LAST_N_DAYS:30
    ORDER BY Amount DESC
    LIMIT 50
];
  • :stage is a bind variable; it is safe from SOQL injection.
  • Assigning a query to a single sObject (Account a = [SELECT ...];) throws a QueryException if zero or more than one row returns.
  • Date literals include TODAY, YESTERDAY, THIS_MONTH, LAST_N_DAYS:n (includes today) and NEXT_N_DAYS:n.
  • IN :idSet filters by a collection.

Aggregate queries

List<AggregateResult> rows = [
    SELECT AccountId, COUNT(Id) total, SUM(Amount) amt
    FROM Opportunity
    GROUP BY AccountId
    HAVING COUNT(Id) > 5
];
for (AggregateResult r : rows) {
    Id accId = (Id) r.get('AccountId');
    Integer total = (Integer) r.get('total');
}

WHERE filters rows before grouping; HAVING filters groups by an aggregate. OFFSET can skip at most 2,000 rows.

SOSL

List<List<SObject>> found = [
    FIND 'Acme*' IN NAME FIELDS
    RETURNING Account(Id, Name), Contact(Id, LastName)
];
List<Account> accs = (List<Account>) found[0];
Use SOQL whenUse SOSL when
You know the object and the fields to filterYou search text across several objects
You need relationship or aggregate queriesThe user types a search term, like a global search
Exact matching on fieldsMatching words in many fields at once

A transaction can run 20 SOSL searches, and each search returns at most 2,000 records.

Dynamic queries

Database.query(queryString) runs a SOQL string built at run time. Bind variables still work inside the string (WHERE Name = :term), and are the safe way to include user input. Database.queryWithBinds() takes a map of bind values and an access level.

DML statements and Database methods

StatementDatabase methodOn error
insert recs;Database.insert(recs, false)Statement: whole list fails and a DmlException is thrown. Method with allOrNone=false: valid records save, errors are returned
update recs;Database.update(recs, false)Same
upsert recs Field;Database.upsert(recs, Field, false)Same
delete recs;Database.delete(recs, false)Same
undelete recs;Database.undelete(recs, false)Same
Database.SaveResult[] results = Database.insert(contacts, false);
for (Integer i = 0; i < results.size(); i++) {
    if (!results[i].isSuccess()) {
        System.debug(contacts[i].LastName + ': ' + results[i].getErrors()[0].getMessage());
    }
}

merge combines up to three records of the same type (Account, Contact, Lead and some others). Inserting a setup object such as User and a non-setup object such as Account in the same transaction causes a mixed DML error; move one to @future or use System.runAs in tests.

A worked example

A nightly import inserts 200 Contacts, and three fail a validation rule. With insert contacts; all 200 fail and a DmlException is thrown. With Database.insert(contacts, false), 197 save and the three failures appear in the SaveResult array, where the code can log them. If the business rule says "save what is valid and report the rest", the Database method with allOrNone = false is the answer.

Exam tip

A SOQL query inside a for loop is the most common wrong answer in this exam. Query once, before the loop, using IN :ids.

How the exam asks about this

  • "Search for a term across Accounts, Contacts and Leads." SOSL.
  • "Save valid records and report failures." Database.insert(records, false).
  • "Filter by a value the user typed." A bind variable, never string concatenation.

What to remember for the exam

  • SOQL reads one object and its relationships; SOSL searches text across objects.
  • Bind variables (:var) prevent SOQL injection.
  • A query assigned to a single sObject must return exactly one row.
  • DML statements are all or none; Database methods with false allow partial success.
  • 20 SOSL searches per transaction, 2,000 records per search; OFFSET up to 2,000.
Watch out

Account a = [SELECT Id FROM Account WHERE Name = 'X']; throws "List has no rows for assignment to SObject" when nothing matches. Query into a List and check its size.

Common mistakes

Using SOSL to filter one object by exact field values. That is SOQL.

Catching a DmlException to save the valid records. The statement already rolled them back; use allOrNone = false.

Concatenating user input into a dynamic query. Use a bind variable.

Check yourself

  • Which query language searches text across several objects at once?

    SOSL, with FIND ... RETURNING.

  • What happens when insert recs; hits one failing record in 200?

    None of the 200 are saved and a DmlException is thrown.

  • What is the difference between WHERE and HAVING?

    WHERE filters rows before grouping; HAVING filters the groups by an aggregate value.

Topic 3 of 7 in Process Automation and Logic

Test yourself on Process Automation and Logic

The full Salesforce Platform Developer question bank has 645 questions, with timed practice exams and an explanation for every answer.

This study guide is independent and is not affiliated with, endorsed by, or sponsored by Salesforce. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.